@saasicat/core 1.0.0-rc.25 → 1.0.0-rc.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +2120 -147
- package/dist/index.d.cts +3525 -182
- package/dist/index.d.ts +3525 -182
- package/dist/index.js +2006 -146
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -23,16 +23,37 @@ __export(index_exports, {
|
|
|
23
23
|
ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES: () => ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES,
|
|
24
24
|
AUTH_ERROR_CODES: () => AUTH_ERROR_CODES,
|
|
25
25
|
BILLING_ERROR_CODES: () => BILLING_ERROR_CODES,
|
|
26
|
+
BLOCKING_COLLECTION_STATUSES: () => BLOCKING_COLLECTION_STATUSES,
|
|
26
27
|
BUNDLE_PRICE_LOOKUP_LIMIT: () => BUNDLE_PRICE_LOOKUP_LIMIT,
|
|
27
28
|
BUNDLE_VERSION_RETIREMENT_CAPABILITY: () => BUNDLE_VERSION_RETIREMENT_CAPABILITY,
|
|
29
|
+
CASE_CLOSED_REFUND_ENDED: () => CASE_CLOSED_REFUND_ENDED,
|
|
30
|
+
CASE_CLOSED_REVERSAL_GIVEN_BACK: () => CASE_CLOSED_REVERSAL_GIVEN_BACK,
|
|
28
31
|
CATALOGUE_KEYS: () => CATALOGUE_KEYS,
|
|
29
32
|
CATALOG_ERROR_CODES: () => CATALOG_ERROR_CODES,
|
|
33
|
+
COLLECTION_FAILURE_REASONS: () => COLLECTION_FAILURE_REASONS,
|
|
34
|
+
COLLECTION_NOT_SENT_PAID: () => COLLECTION_NOT_SENT_PAID,
|
|
35
|
+
COLLECTION_NOT_SENT_REVERSED: () => COLLECTION_NOT_SENT_REVERSED,
|
|
36
|
+
COLLECTION_RETRY_AFTER_DAYS: () => COLLECTION_RETRY_AFTER_DAYS,
|
|
30
37
|
CONTRACT_ERROR_CODES: () => CONTRACT_ERROR_CODES,
|
|
31
38
|
CUSTOM_LIMITS_KEYS: () => CUSTOM_LIMITS_KEYS,
|
|
39
|
+
DIRECT_DEBIT_LEAD_DAYS: () => DIRECT_DEBIT_LEAD_DAYS,
|
|
40
|
+
ENDED_REFUND_STATUSES: () => ENDED_REFUND_STATUSES,
|
|
32
41
|
ERROR_MESSAGES_DE: () => ERROR_MESSAGES_DE,
|
|
33
42
|
ERROR_MESSAGES_EN: () => ERROR_MESSAGES_EN,
|
|
34
43
|
FEATURE_NOT_LICENSED: () => FEATURE_NOT_LICENSED,
|
|
44
|
+
FEATURE_WITHDRAWAL_CAPABILITY: () => FEATURE_WITHDRAWAL_CAPABILITY,
|
|
45
|
+
FEATURE_WITHDRAWAL_REASON_MAX_LENGTH: () => FEATURE_WITHDRAWAL_REASON_MAX_LENGTH,
|
|
46
|
+
FOLLOWED_UP_COLLECTION_STATUSES: () => FOLLOWED_UP_COLLECTION_STATUSES,
|
|
35
47
|
ForeignPaymentMethodReferenceError: () => ForeignPaymentMethodReferenceError,
|
|
48
|
+
GIVING_BACK_CREDIT_ENTRY_KINDS: () => GIVING_BACK_CREDIT_ENTRY_KINDS,
|
|
49
|
+
INVOICE_ARCHIVE_TRIGGERS: () => INVOICE_ARCHIVE_TRIGGERS,
|
|
50
|
+
INVOICE_COLLECTION_ASK_EVERY_MS: () => INVOICE_COLLECTION_ASK_EVERY_MS,
|
|
51
|
+
INVOICE_COLLECTION_LEASE_MS: () => INVOICE_COLLECTION_LEASE_MS,
|
|
52
|
+
INVOICE_COLLECTION_STATUSES: () => INVOICE_COLLECTION_STATUSES,
|
|
53
|
+
INVOICE_DOCUMENT_CAPABILITY: () => INVOICE_DOCUMENT_CAPABILITY,
|
|
54
|
+
INVOICE_ERROR_CODES: () => INVOICE_ERROR_CODES,
|
|
55
|
+
INVOICE_SEQUENCE_DIGITS: () => INVOICE_SEQUENCE_DIGITS,
|
|
56
|
+
INVOICE_SETTLEMENT_KINDS: () => INVOICE_SETTLEMENT_KINDS,
|
|
36
57
|
LEGAL_IDENTITY_FIELDS: () => LEGAL_IDENTITY_FIELDS,
|
|
37
58
|
MAINTENANCE_CAPABILITY: () => MAINTENANCE_CAPABILITY,
|
|
38
59
|
MAINTENANCE_ERROR_CODES: () => MAINTENANCE_ERROR_CODES,
|
|
@@ -58,24 +79,41 @@ __export(index_exports, {
|
|
|
58
79
|
PersistenceCapabilityError: () => PersistenceCapabilityError,
|
|
59
80
|
PersistenceRefusal: () => PersistenceRefusal,
|
|
60
81
|
PlatformUserExistsError: () => PlatformUserExistsError,
|
|
82
|
+
RECONCILIATION_CASE_KINDS: () => RECONCILIATION_CASE_KINDS,
|
|
83
|
+
RECONCILIATION_CLOSE_CAPABILITY: () => RECONCILIATION_CLOSE_CAPABILITY,
|
|
84
|
+
RECONCILIATION_NOTE_MAX_LENGTH: () => RECONCILIATION_NOTE_MAX_LENGTH,
|
|
85
|
+
RECONCILIATION_READ_CAPABILITY: () => RECONCILIATION_READ_CAPABILITY,
|
|
61
86
|
REGISTRATION_ERROR_CODES: () => REGISTRATION_ERROR_CODES,
|
|
62
87
|
REGISTRATION_RESUME_TTL_MINUTES: () => REGISTRATION_RESUME_TTL_MINUTES,
|
|
63
88
|
REGISTRATION_STEP_BY_STATUS: () => REGISTRATION_STEP_BY_STATUS,
|
|
89
|
+
REOPENING_SETTLEMENT_KINDS: () => REOPENING_SETTLEMENT_KINDS,
|
|
64
90
|
RETIREMENT_REMINDER_LEAD_DAYS: () => RETIREMENT_REMINDER_LEAD_DAYS,
|
|
91
|
+
REVERSIBLE_COLLECTION_STATUSES: () => REVERSIBLE_COLLECTION_STATUSES,
|
|
65
92
|
SETTINGS_ERROR_CODES: () => SETTINGS_ERROR_CODES,
|
|
66
93
|
SETUP_ERROR_CODES: () => SETUP_ERROR_CODES,
|
|
67
94
|
SUBSCRIBER_ACCOUNT_CAPABILITY: () => SUBSCRIBER_ACCOUNT_CAPABILITY,
|
|
68
95
|
SUBSCRIBER_ATTENTION_CAPABILITY: () => SUBSCRIBER_ATTENTION_CAPABILITY,
|
|
69
96
|
SUBSCRIBER_ATTENTION_PAGE_SIZE: () => SUBSCRIBER_ATTENTION_PAGE_SIZE,
|
|
97
|
+
SUBSCRIBER_CHARGE_ORIGINS: () => SUBSCRIBER_CHARGE_ORIGINS,
|
|
98
|
+
SUBSCRIBER_CHARGE_SOURCES: () => SUBSCRIBER_CHARGE_SOURCES,
|
|
70
99
|
SUBSCRIBER_CORRECTION_CAPABILITY: () => SUBSCRIBER_CORRECTION_CAPABILITY,
|
|
100
|
+
SUBSCRIBER_CREDIT_CAPABILITY: () => SUBSCRIBER_CREDIT_CAPABILITY,
|
|
101
|
+
SUBSCRIBER_CREDIT_ENTRY_KINDS: () => SUBSCRIBER_CREDIT_ENTRY_KINDS,
|
|
71
102
|
SUBSCRIBER_ERROR_CODES: () => SUBSCRIBER_ERROR_CODES,
|
|
72
103
|
SUBSCRIBER_INVOICE_ADDRESS_FIELDS: () => SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
|
|
104
|
+
SUBSCRIBER_PAYMENTS_CAPABILITY: () => SUBSCRIBER_PAYMENTS_CAPABILITY,
|
|
105
|
+
SUBSCRIBER_PAYOUT_CAPABILITY: () => SUBSCRIBER_PAYOUT_CAPABILITY,
|
|
106
|
+
SUBSCRIBER_REFUND_STATUSES: () => SUBSCRIBER_REFUND_STATUSES,
|
|
73
107
|
SUBSCRIBER_STANDING_CAPABILITY: () => SUBSCRIBER_STANDING_CAPABILITY,
|
|
74
108
|
SUBSCRIBER_TAX_ORIGIN_FIELDS: () => SUBSCRIBER_TAX_ORIGIN_FIELDS,
|
|
75
109
|
TAX_ERROR_CODES: () => TAX_ERROR_CODES,
|
|
76
110
|
TAX_TREATMENT_KINDS: () => TAX_TREATMENT_KINDS,
|
|
111
|
+
UNSETTLED_COLLECTION_STATUSES: () => UNSETTLED_COLLECTION_STATUSES,
|
|
77
112
|
VERSION_RETIREMENT_CAPABILITY: () => VERSION_RETIREMENT_CAPABILITY,
|
|
113
|
+
addDaysToDay: () => addDaysToDay,
|
|
114
|
+
announcedCollectionDay: () => announcedCollectionDay,
|
|
78
115
|
applyPromo: () => applyPromo,
|
|
116
|
+
asXmlReadsIt: () => asXmlReadsIt,
|
|
79
117
|
assertPersistenceCapabilities: () => assertPersistenceCapabilities,
|
|
80
118
|
buildActivePlanVersionWhere: () => buildActivePlanVersionWhere,
|
|
81
119
|
buildActiveVersionWhere: () => buildActiveVersionWhere,
|
|
@@ -85,38 +123,69 @@ __export(index_exports, {
|
|
|
85
123
|
bundleStemDefaults: () => bundleStemDefaults,
|
|
86
124
|
canonicalJson: () => canonicalJson,
|
|
87
125
|
canonicalVatId: () => canonicalVatId,
|
|
126
|
+
casesCovered: () => casesCovered,
|
|
88
127
|
catalogDraftExists: () => catalogDraftExists,
|
|
89
128
|
catalogVersionAlreadyPublished: () => catalogVersionAlreadyPublished,
|
|
90
129
|
catalogVersionGone: () => catalogVersionGone,
|
|
130
|
+
chargedAgainOn: () => chargedAgainOn,
|
|
91
131
|
classifyBundleVersionDiff: () => classifyBundleVersionDiff,
|
|
92
132
|
classifyIssuerChange: () => classifyIssuerChange,
|
|
93
133
|
classifyPlanDiff: () => classifyPlanDiff,
|
|
94
134
|
classifyVersionOffer: () => classifyVersionOffer,
|
|
95
135
|
collectUnsatisfiedRequires: () => collectUnsatisfiedRequires,
|
|
136
|
+
collectionAttemptKey: () => collectionAttemptKey,
|
|
137
|
+
collectionOutcomeColumns: () => collectionOutcomeColumns,
|
|
138
|
+
collectionStatusOf: () => collectionStatusOf,
|
|
139
|
+
columnAmountOf: () => columnAmountOf,
|
|
96
140
|
computeIncludedVat: () => computeIncludedVat,
|
|
97
141
|
concurrencyGate: () => concurrencyGate,
|
|
98
142
|
concurrencyGateOf: () => concurrencyGateOf,
|
|
99
143
|
contractPartiesOf: () => contractPartiesOf,
|
|
100
144
|
coverageExcludingSelf: () => coverageExcludingSelf,
|
|
145
|
+
coversLifted: () => coversLifted,
|
|
146
|
+
coversOf: () => coversOf,
|
|
101
147
|
customLimitsReader: () => customLimitsReader,
|
|
148
|
+
dayInZone: () => dayInZone,
|
|
102
149
|
definedFields: () => definedFields,
|
|
103
150
|
diffSettings: () => diffSettings,
|
|
151
|
+
directDebitLeadDaysOf: () => directDebitLeadDaysOf,
|
|
152
|
+
draftSubscriptionInvoice: () => draftSubscriptionInvoice,
|
|
153
|
+
endedAtOnceSubjectOf: () => endedAtOnceSubjectOf,
|
|
154
|
+
featureWithdrawalRowOf: () => featureWithdrawalRowOf,
|
|
155
|
+
featuresWithdrawnAt: () => featuresWithdrawnAt,
|
|
104
156
|
formatCustomerNumber: () => formatCustomerNumber,
|
|
105
157
|
formatErrorMessage: () => formatErrorMessage,
|
|
158
|
+
formatInvoiceNumber: () => formatInvoiceNumber,
|
|
106
159
|
grossFromNet: () => grossFromNet,
|
|
160
|
+
holdsOnlyXmlCharacters: () => holdsOnlyXmlCharacters,
|
|
107
161
|
identityCorrectionDelta: () => identityCorrectionDelta,
|
|
162
|
+
inWithdrawalOrder: () => inWithdrawalOrder,
|
|
163
|
+
invoiceArchiveProtectionProblem: () => invoiceArchiveProtectionProblem,
|
|
164
|
+
invoiceCollectionColumns: () => invoiceCollectionColumns,
|
|
165
|
+
invoiceContentDraftOf: () => invoiceContentDraftOf,
|
|
166
|
+
invoiceFooterOf: () => invoiceFooterOf,
|
|
167
|
+
invoiceGroupsOf: () => invoiceGroupsOf,
|
|
168
|
+
invoiceIssuerOf: () => invoiceIssuerOf,
|
|
169
|
+
invoicePaymentOf: () => invoicePaymentOf,
|
|
108
170
|
isBundleRedundant: () => isBundleRedundant,
|
|
171
|
+
isChargedAgain: () => isChargedAgain,
|
|
172
|
+
isEndedRefund: () => isEndedRefund,
|
|
173
|
+
isFeatureWithdrawnAt: () => isFeatureWithdrawnAt,
|
|
109
174
|
isForeignPaymentMethodReferenceError: () => isForeignPaymentMethodReferenceError,
|
|
175
|
+
isInvoiceDocumentChecksum: () => isInvoiceDocumentChecksum,
|
|
110
176
|
isMaintenanceAnnouncementLapsed: () => isMaintenanceAnnouncementLapsed,
|
|
111
177
|
isMaintenanceOverrun: () => isMaintenanceOverrun,
|
|
112
178
|
isPaymentCallbackRejectedError: () => isPaymentCallbackRejectedError,
|
|
113
179
|
isPersistenceRefusal: () => isPersistenceRefusal,
|
|
114
180
|
isPlatformUserExistsError: () => isPlatformUserExistsError,
|
|
181
|
+
isRetryableCollectionFailure: () => isRetryableCollectionFailure,
|
|
115
182
|
isVersionActiveAt: () => isVersionActiveAt,
|
|
116
183
|
isVersionEditable: () => isVersionEditable,
|
|
117
184
|
issuerIdentityOf: () => issuerIdentityOf,
|
|
118
185
|
keepOneRecommended: () => keepOneRecommended,
|
|
119
186
|
keepsVatIdCheck: () => keepsVatIdCheck,
|
|
187
|
+
lastCollectionOf: () => lastCollectionOf,
|
|
188
|
+
lastDayInZone: () => lastDayInZone,
|
|
120
189
|
maintenanceRetryAfterSeconds: () => maintenanceRetryAfterSeconds,
|
|
121
190
|
maintenanceStatusOf: () => maintenanceStatusOf,
|
|
122
191
|
maintenanceWindowStatusOf: () => maintenanceWindowStatusOf,
|
|
@@ -125,14 +194,22 @@ __export(index_exports, {
|
|
|
125
194
|
membersLeftOut: () => membersLeftOut,
|
|
126
195
|
missingRequiresFor: () => missingRequiresFor,
|
|
127
196
|
movedIdentityFields: () => movedIdentityFields,
|
|
197
|
+
netByInvoice: () => netByInvoice,
|
|
128
198
|
netFromGross: () => netFromGross,
|
|
199
|
+
nextWithdrawalChange: () => nextWithdrawalChange,
|
|
129
200
|
noActivePlanVersion: () => noActivePlanVersion,
|
|
201
|
+
openOfCredit: () => openOfCredit,
|
|
202
|
+
owedOn: () => owedOn,
|
|
203
|
+
paidOutByCredit: () => paidOutByCredit,
|
|
130
204
|
parseZonedInstant: () => parseZonedInstant,
|
|
205
|
+
paymentSettlement: () => paymentSettlement,
|
|
206
|
+
payoutEntryColumns: () => payoutEntryColumns,
|
|
131
207
|
percentOf: () => percentOf,
|
|
132
208
|
pickActivePromo: () => pickActivePromo,
|
|
133
209
|
planCatalogSettingsOf: () => planCatalogSettingsOf,
|
|
134
210
|
planKeyTaken: () => planKeyTaken,
|
|
135
211
|
planNotInCatalog: () => planNotInCatalog,
|
|
212
|
+
plannedCollectionColumns: () => plannedCollectionColumns,
|
|
136
213
|
previousUtcDay: () => previousUtcDay,
|
|
137
214
|
promoCodeTaken: () => promoCodeTaken,
|
|
138
215
|
promoStatus: () => promoStatus,
|
|
@@ -141,56 +218,92 @@ __export(index_exports, {
|
|
|
141
218
|
readCustomLimits: () => readCustomLimits,
|
|
142
219
|
readQuotaRecord: () => readQuotaRecord,
|
|
143
220
|
readQuotaValue: () => readQuotaValue,
|
|
221
|
+
reconciliationCaseColumns: () => reconciliationCaseColumns,
|
|
144
222
|
recordedIssuerIdentity: () => recordedIssuerIdentity,
|
|
223
|
+
refundEntryColumns: () => refundEntryColumns,
|
|
224
|
+
refundParts: () => refundParts,
|
|
145
225
|
refuseForeignPaymentMethodReference: () => refuseForeignPaymentMethodReference,
|
|
226
|
+
refusesFirstAttempt: () => refusesFirstAttempt,
|
|
227
|
+
refusesPlannedAttempt: () => refusesPlannedAttempt,
|
|
228
|
+
reinstatementParts: () => reinstatementParts,
|
|
146
229
|
resolveBundleAvailability: () => resolveBundleAvailability,
|
|
147
230
|
resolveErrorMessage: () => resolveErrorMessage,
|
|
148
231
|
retirementCostsTheSubscription: () => retirementCostsTheSubscription,
|
|
149
232
|
retirementReminderDueAt: () => retirementReminderDueAt,
|
|
150
233
|
retirementReminderIsDue: () => retirementReminderIsDue,
|
|
151
234
|
retirementSwitchTerms: () => retirementSwitchTerms,
|
|
235
|
+
reversalEntryColumns: () => reversalEntryColumns,
|
|
152
236
|
roundToCents: () => roundToCents,
|
|
153
237
|
sameLegalIdentity: () => sameLegalIdentity,
|
|
154
238
|
scheduledChangeAfterWrite: () => scheduledChangeAfterWrite,
|
|
155
239
|
selectChargeableBundles: () => selectChargeableBundles,
|
|
240
|
+
sentCollectionOf: () => sentCollectionOf,
|
|
156
241
|
settingsSubtreeOf: () => settingsSubtreeOf,
|
|
242
|
+
settledAmountOf: () => settledAmountOf,
|
|
157
243
|
startOfUtcDay: () => startOfUtcDay,
|
|
158
244
|
subscriberChargeColumns: () => subscriberChargeColumns,
|
|
159
245
|
subscriberFromRegistration: () => subscriberFromRegistration,
|
|
246
|
+
subscriberPaymentColumns: () => subscriberPaymentColumns,
|
|
160
247
|
subscriberPaymentMethodColumns: () => subscriberPaymentMethodColumns,
|
|
248
|
+
subscriberRefundColumns: () => subscriberRefundColumns,
|
|
249
|
+
subscriberReversalColumns: () => subscriberReversalColumns,
|
|
161
250
|
subscriptionBundleAlreadyCancelled: () => subscriptionBundleAlreadyCancelled,
|
|
162
251
|
subscriptionBundleGone: () => subscriptionBundleGone,
|
|
163
252
|
subscriptionContractGone: () => subscriptionContractGone,
|
|
164
253
|
subscriptionGone: () => subscriptionGone,
|
|
254
|
+
subscriptionInvoiceChargeInvoiced: () => subscriptionInvoiceChargeInvoiced,
|
|
255
|
+
subscriptionInvoiceColumns: () => subscriptionInvoiceColumns,
|
|
256
|
+
subscriptionInvoiceDocumentChanged: () => subscriptionInvoiceDocumentChanged,
|
|
257
|
+
subscriptionInvoiceLineColumns: () => subscriptionInvoiceLineColumns,
|
|
165
258
|
sumToCents: () => sumToCents,
|
|
166
259
|
taxOriginOf: () => taxOriginOf,
|
|
167
260
|
taxOriginWrite: () => taxOriginWrite,
|
|
261
|
+
taxPerRate: () => taxPerRate,
|
|
168
262
|
taxTreatmentToJson: () => taxTreatmentToJson,
|
|
263
|
+
toArchivedInvoiceDocument: () => toArchivedInvoiceDocument,
|
|
169
264
|
toBundleStemRow: () => toBundleStemRow,
|
|
170
265
|
toBundleVersionRetirementRecord: () => toBundleVersionRetirementRecord,
|
|
171
266
|
toCents: () => toCents,
|
|
267
|
+
toCollectionAnnouncedNotice: () => toCollectionAnnouncedNotice,
|
|
268
|
+
toCollectionFailedNotice: () => toCollectionFailedNotice,
|
|
269
|
+
toCollectionReversedNotice: () => toCollectionReversedNotice,
|
|
172
270
|
toContractLineItemRecord: () => toContractLineItemRecord,
|
|
173
271
|
toDecimalString: () => toDecimalString,
|
|
272
|
+
toFeatureWithdrawalRecord: () => toFeatureWithdrawalRecord,
|
|
273
|
+
toInvoiceCollectionRecord: () => toInvoiceCollectionRecord,
|
|
174
274
|
toMaintenanceWindowRecord: () => toMaintenanceWindowRecord,
|
|
175
275
|
toPlanRow: () => toPlanRow,
|
|
176
276
|
toPlanVersionRow: () => toPlanVersionRow,
|
|
177
277
|
toRunningContractIssuer: () => toRunningContractIssuer,
|
|
178
278
|
toSubscriberChargeRecord: () => toSubscriberChargeRecord,
|
|
179
279
|
toSubscriberCorrectionRecord: () => toSubscriberCorrectionRecord,
|
|
280
|
+
toSubscriberCreditEntryRecord: () => toSubscriberCreditEntryRecord,
|
|
281
|
+
toSubscriberCreditRecord: () => toSubscriberCreditRecord,
|
|
180
282
|
toSubscriberPaymentMethodRecord: () => toSubscriberPaymentMethodRecord,
|
|
283
|
+
toSubscriberPaymentRecord: () => toSubscriberPaymentRecord,
|
|
284
|
+
toSubscriberReconciliationCaseRecord: () => toSubscriberReconciliationCaseRecord,
|
|
181
285
|
toSubscriberRecord: () => toSubscriberRecord,
|
|
286
|
+
toSubscriberRefundRecord: () => toSubscriberRefundRecord,
|
|
287
|
+
toSubscriberReversalRecord: () => toSubscriberReversalRecord,
|
|
182
288
|
toSubscriberTaxOriginChangeRecord: () => toSubscriberTaxOriginChangeRecord,
|
|
183
289
|
toSubscriberVatIdCheckRecord: () => toSubscriberVatIdCheckRecord,
|
|
184
290
|
toSubscriptionBundleRecord: () => toSubscriptionBundleRecord,
|
|
185
291
|
toSubscriptionContractRecord: () => toSubscriptionContractRecord,
|
|
292
|
+
toSubscriptionInvoiceDocumentRecord: () => toSubscriptionInvoiceDocumentRecord,
|
|
293
|
+
toSubscriptionInvoiceRecord: () => toSubscriptionInvoiceRecord,
|
|
186
294
|
toSubscriptionNoticeRecord: () => toSubscriptionNoticeRecord,
|
|
187
295
|
toTaxOriginValues: () => toTaxOriginValues,
|
|
188
296
|
toTaxTreatment: () => toTaxTreatment,
|
|
189
297
|
toVersionRetirementRecord: () => toVersionRetirementRecord,
|
|
298
|
+
utcMidnightOf: () => utcMidnightOf,
|
|
299
|
+
vatCategoryOf: () => vatCategoryOf,
|
|
190
300
|
vatIdCheckFromStore: () => vatIdCheckFromStore,
|
|
191
301
|
versionOnSale: () => versionOnSale,
|
|
192
302
|
versionOnSaleOrNext: () => versionOnSaleOrNext,
|
|
193
|
-
versionSale: () => versionSale
|
|
303
|
+
versionSale: () => versionSale,
|
|
304
|
+
withdrawalParts: () => withdrawalParts,
|
|
305
|
+
withdrawnFeaturesOf: () => withdrawnFeaturesOf,
|
|
306
|
+
yearOfDay: () => yearOfDay
|
|
194
307
|
});
|
|
195
308
|
module.exports = __toCommonJS(index_exports);
|
|
196
309
|
|
|
@@ -353,6 +466,22 @@ function computeIncludedVat(gross, vatRate) {
|
|
|
353
466
|
const rate = exactDecimal(vatRate);
|
|
354
467
|
return amountOf(cents(exactDecimal(gross), rate, sum(HUNDRED, rate)));
|
|
355
468
|
}
|
|
469
|
+
function taxPerRate(lines) {
|
|
470
|
+
const nets = /* @__PURE__ */ new Map();
|
|
471
|
+
for (const line of lines) {
|
|
472
|
+
const key = String(line.rate);
|
|
473
|
+
const entry = nets.get(key) ?? { rate: line.rate, terms: [] };
|
|
474
|
+
entry.terms.push(line.net);
|
|
475
|
+
nets.set(key, entry);
|
|
476
|
+
}
|
|
477
|
+
const rates = [...nets.values()].sort((a, b) => a.rate - b.rate).map(({ rate, terms }) => {
|
|
478
|
+
const net2 = sumToCents(...terms);
|
|
479
|
+
return { rate, net: net2, tax: percentOf(net2, rate) };
|
|
480
|
+
});
|
|
481
|
+
const net = sumToCents(...rates.map((entry) => entry.net));
|
|
482
|
+
const tax = sumToCents(...rates.map((entry) => entry.tax));
|
|
483
|
+
return { rates, net, tax, gross: sumToCents(net, tax) };
|
|
484
|
+
}
|
|
356
485
|
|
|
357
486
|
// src/promotion.types.ts
|
|
358
487
|
function promoStatus(promo, today = /* @__PURE__ */ new Date()) {
|
|
@@ -603,7 +732,13 @@ var KINDS = {
|
|
|
603
732
|
"version-retirement-reminder": true,
|
|
604
733
|
"bundle-version-retired": true,
|
|
605
734
|
"bundle-version-retirement-reminder": true,
|
|
606
|
-
"bundle-version-offered": true
|
|
735
|
+
"bundle-version-offered": true,
|
|
736
|
+
"feature-withdrawn": true,
|
|
737
|
+
"feature-withdrawal-lifted": true,
|
|
738
|
+
"ended-at-once": true,
|
|
739
|
+
"collection-failed": true,
|
|
740
|
+
"collection-announced": true,
|
|
741
|
+
"collection-reversed": true
|
|
607
742
|
};
|
|
608
743
|
function toSubscriptionNoticeRecord(row) {
|
|
609
744
|
return {
|
|
@@ -734,6 +869,106 @@ function toBundleVersionRetirementRecord(row) {
|
|
|
734
869
|
};
|
|
735
870
|
}
|
|
736
871
|
|
|
872
|
+
// src/feature-withdrawal.types.ts
|
|
873
|
+
var FEATURE_WITHDRAWAL_REASON_MAX_LENGTH = 1e3;
|
|
874
|
+
function isFeatureWithdrawnAt(withdrawal, at) {
|
|
875
|
+
return withdrawal.effectiveFrom <= at && (withdrawal.liftedFrom === null || at < withdrawal.liftedFrom);
|
|
876
|
+
}
|
|
877
|
+
function featuresWithdrawnAt(withdrawals, at) {
|
|
878
|
+
return new Set(
|
|
879
|
+
withdrawals.filter((withdrawal) => isFeatureWithdrawnAt(withdrawal, at)).map((withdrawal) => withdrawal.featureKey)
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
function nextWithdrawalChange(withdrawals, at) {
|
|
883
|
+
let next = null;
|
|
884
|
+
for (const { effectiveFrom, liftedFrom } of withdrawals) {
|
|
885
|
+
for (const moment of [effectiveFrom, liftedFrom]) {
|
|
886
|
+
if (moment === null || moment <= at) continue;
|
|
887
|
+
if (next === null || moment < next) next = moment;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return next;
|
|
891
|
+
}
|
|
892
|
+
var FEATURE_WITHDRAWAL_CAPABILITY = "features.withdraw";
|
|
893
|
+
function featureWithdrawalRowOf(record) {
|
|
894
|
+
return {
|
|
895
|
+
id: record.id,
|
|
896
|
+
featureKey: record.featureKey,
|
|
897
|
+
reason: record.reason,
|
|
898
|
+
effectiveFrom: record.effectiveFrom.toISOString(),
|
|
899
|
+
liftedFrom: record.liftedFrom?.toISOString() ?? null,
|
|
900
|
+
reductions: record.reductions,
|
|
901
|
+
announcedAt: record.announcedAt.toISOString(),
|
|
902
|
+
announcedBy: record.announcedBy,
|
|
903
|
+
liftedAt: record.liftedAt?.toISOString() ?? null,
|
|
904
|
+
liftedBy: record.liftedBy,
|
|
905
|
+
toldAt: record.toldAt?.toISOString() ?? null
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function withdrawnFeaturesOf(withdrawals, now) {
|
|
909
|
+
return withdrawals.filter(
|
|
910
|
+
({ effectiveFrom, liftedFrom }) => liftedFrom === null || liftedFrom > now && effectiveFrom < liftedFrom
|
|
911
|
+
).sort((a, b) => a.effectiveFrom.getTime() - b.effectiveFrom.getTime()).map((withdrawal) => ({
|
|
912
|
+
featureKey: withdrawal.featureKey,
|
|
913
|
+
reason: withdrawal.reason,
|
|
914
|
+
effectiveFrom: withdrawal.effectiveFrom.toISOString(),
|
|
915
|
+
liftedFrom: withdrawal.liftedFrom?.toISOString() ?? null
|
|
916
|
+
}));
|
|
917
|
+
}
|
|
918
|
+
function endedAtOnceSubjectOf(subscriptionOrBookingId, at) {
|
|
919
|
+
return `${subscriptionOrBookingId}@${at.toISOString()}`;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/feature-withdrawal-mapping.ts
|
|
923
|
+
var TABLE2 = "feature_withdrawals";
|
|
924
|
+
var KINDS2 = { plan: true, bundle: true };
|
|
925
|
+
var CYCLES = { MONTHLY: true, YEARLY: true };
|
|
926
|
+
function toFeatureWithdrawalRecord(row) {
|
|
927
|
+
return {
|
|
928
|
+
id: row.id,
|
|
929
|
+
featureKey: row.featureKey,
|
|
930
|
+
reason: row.reason,
|
|
931
|
+
effectiveFrom: row.effectiveFrom,
|
|
932
|
+
liftedFrom: row.liftedFrom,
|
|
933
|
+
reductions: reductionsOf(row),
|
|
934
|
+
announcedAt: row.announcedAt,
|
|
935
|
+
announcedBy: row.announcedBy,
|
|
936
|
+
liftedAt: row.liftedAt,
|
|
937
|
+
liftedBy: row.liftedBy,
|
|
938
|
+
toldAt: row.toldAt
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function reductionsOf(row) {
|
|
942
|
+
if (!Array.isArray(row.reductions)) {
|
|
943
|
+
throw new Error(`${TABLE2} row '${row.id}' holds reductions that are not a list.`);
|
|
944
|
+
}
|
|
945
|
+
return row.reductions.map((entry, index) => {
|
|
946
|
+
const column = `reductions[${index}]`;
|
|
947
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
948
|
+
throw new Error(`${TABLE2} row '${row.id}' holds ${column} that is not an object.`);
|
|
949
|
+
}
|
|
950
|
+
const { kind, key, billingCycle, amountNet } = entry;
|
|
951
|
+
if (typeof kind !== "string" || typeof key !== "string" || typeof billingCycle !== "string" || typeof amountNet !== "number" || !Number.isFinite(amountNet)) {
|
|
952
|
+
throw new Error(
|
|
953
|
+
`${TABLE2} row '${row.id}' holds ${column} without a kind, a key, a billing cycle and a finite amount.`
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
const source = { table: TABLE2, id: row.id };
|
|
957
|
+
return {
|
|
958
|
+
kind: oneOf(Object.keys(KINDS2), kind, {
|
|
959
|
+
...source,
|
|
960
|
+
column: `${column}.kind`
|
|
961
|
+
}),
|
|
962
|
+
key,
|
|
963
|
+
billingCycle: oneOf(Object.keys(CYCLES), billingCycle, {
|
|
964
|
+
...source,
|
|
965
|
+
column: `${column}.billingCycle`
|
|
966
|
+
}),
|
|
967
|
+
amountNet
|
|
968
|
+
};
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
737
972
|
// src/scheduled-change-after-write.ts
|
|
738
973
|
var NO_SCHEDULED_CHANGE = {
|
|
739
974
|
pendingPlan: null,
|
|
@@ -870,6 +1105,37 @@ function parseZonedInstant(text) {
|
|
|
870
1105
|
return Number.isNaN(moment.getTime()) ? null : moment;
|
|
871
1106
|
}
|
|
872
1107
|
|
|
1108
|
+
// src/zoned-day.ts
|
|
1109
|
+
function dayInZone(at, timeZone) {
|
|
1110
|
+
if (typeof timeZone !== "string" || timeZone === "") {
|
|
1111
|
+
throw new RangeError("A day is read in a named time zone.");
|
|
1112
|
+
}
|
|
1113
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
1114
|
+
timeZone,
|
|
1115
|
+
year: "numeric",
|
|
1116
|
+
month: "2-digit",
|
|
1117
|
+
day: "2-digit"
|
|
1118
|
+
}).formatToParts(at);
|
|
1119
|
+
const part = (type) => parts.find((candidate) => candidate.type === type)?.value;
|
|
1120
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
1121
|
+
}
|
|
1122
|
+
function lastDayInZone(period, timeZone) {
|
|
1123
|
+
if (!(period.until.getTime() > period.from.getTime())) {
|
|
1124
|
+
throw new RangeError("A period ends after it begins.");
|
|
1125
|
+
}
|
|
1126
|
+
return dayInZone(new Date(period.until.getTime() - 1), timeZone);
|
|
1127
|
+
}
|
|
1128
|
+
function addDaysToDay(day, days) {
|
|
1129
|
+
if (!Number.isInteger(days)) throw new RangeError("Days are counted in whole days.");
|
|
1130
|
+
const [year, month, date] = day.split("-").map(Number);
|
|
1131
|
+
if (!year || !month || !date) throw new RangeError(`${day} is not a day.`);
|
|
1132
|
+
const moved = new Date(Date.UTC(year, month - 1, date + days));
|
|
1133
|
+
return moved.toISOString().slice(0, 10);
|
|
1134
|
+
}
|
|
1135
|
+
function yearOfDay(day) {
|
|
1136
|
+
return Number(day.slice(0, 4));
|
|
1137
|
+
}
|
|
1138
|
+
|
|
873
1139
|
// src/settings-subtree.ts
|
|
874
1140
|
var CATALOGUE_KEYS = /* @__PURE__ */ new Set([
|
|
875
1141
|
"schemaVersion",
|
|
@@ -1327,6 +1593,80 @@ var BILLING_ERROR_CODES = {
|
|
|
1327
1593
|
* show it instead of guessing.
|
|
1328
1594
|
*/
|
|
1329
1595
|
BUNDLE_VERSION_OFFER_CHANGED: "BUNDLE_VERSION_OFFER_CHANGED",
|
|
1596
|
+
// ── withdrawing a feature for an external reason ──
|
|
1597
|
+
/**
|
|
1598
|
+
* Withdrawing a feature is not offered: the installation's stores cannot
|
|
1599
|
+
* list everybody a withdrawal would reach.
|
|
1600
|
+
*/
|
|
1601
|
+
FEATURE_WITHDRAWAL_UNAVAILABLE: "FEATURE_WITHDRAWAL_UNAVAILABLE",
|
|
1602
|
+
/** The catalogue knows no feature of that key, so there is nothing to withdraw. Carries `featureKey`. */
|
|
1603
|
+
FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "FEATURE_WITHDRAWAL_FEATURE_UNKNOWN",
|
|
1604
|
+
/**
|
|
1605
|
+
* The date names a moment before now. A feature is withdrawn from the
|
|
1606
|
+
* moment it is announced or from a later date: the days before were
|
|
1607
|
+
* delivered.
|
|
1608
|
+
*/
|
|
1609
|
+
FEATURE_WITHDRAWAL_DATE_IN_PAST: "FEATURE_WITHDRAWAL_DATE_IN_PAST",
|
|
1610
|
+
/** A withdrawal of the feature is not lifted yet; one at a time. Carries `featureKey`. */
|
|
1611
|
+
FEATURE_WITHDRAWAL_OPEN: "FEATURE_WITHDRAWAL_OPEN",
|
|
1612
|
+
/**
|
|
1613
|
+
* A withdrawal of the feature is lifted only from a date after the one
|
|
1614
|
+
* named, so the two would overlap. Carries `featureKey` and the `date` a
|
|
1615
|
+
* new one may begin on.
|
|
1616
|
+
*/
|
|
1617
|
+
FEATURE_WITHDRAWAL_OVERLAPS: "FEATURE_WITHDRAWAL_OVERLAPS",
|
|
1618
|
+
/**
|
|
1619
|
+
* A reduction names a plan or an add-on, in a rhythm, that no line the
|
|
1620
|
+
* withdrawal reaches is. Carries `kind`, `key` and `billingCycle`.
|
|
1621
|
+
*/
|
|
1622
|
+
FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED",
|
|
1623
|
+
/** A reduction is named twice. Carries `kind`, `key` and `billingCycle`. */
|
|
1624
|
+
FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE",
|
|
1625
|
+
/**
|
|
1626
|
+
* A reduction is more than the lowest price among the lines it reduces:
|
|
1627
|
+
* a price is reduced to nothing at most. Carries `key`, `billingCycle` and
|
|
1628
|
+
* that `price`.
|
|
1629
|
+
*/
|
|
1630
|
+
FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE",
|
|
1631
|
+
/**
|
|
1632
|
+
* The subscriptions the withdrawal reaches changed since the preview the
|
|
1633
|
+
* operator looked at. Nothing was announced; carries the current `preview`.
|
|
1634
|
+
*/
|
|
1635
|
+
FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "FEATURE_WITHDRAWAL_PREVIEW_CHANGED",
|
|
1636
|
+
/** No withdrawal has that id. Carries `withdrawalId`. */
|
|
1637
|
+
FEATURE_WITHDRAWAL_NOT_FOUND: "FEATURE_WITHDRAWAL_NOT_FOUND",
|
|
1638
|
+
/** The withdrawal was lifted already. Carries the `date` it is lifted from. */
|
|
1639
|
+
FEATURE_WITHDRAWAL_ALREADY_LIFTED: "FEATURE_WITHDRAWAL_ALREADY_LIFTED",
|
|
1640
|
+
/**
|
|
1641
|
+
* Nobody the withdrawal reaches has been told of it yet, and telling them
|
|
1642
|
+
* failed just now: a withdrawal is lifted only once they were. Nothing was
|
|
1643
|
+
* lifted; the next try, or the quarter-hourly run, tells them first.
|
|
1644
|
+
*/
|
|
1645
|
+
FEATURE_WITHDRAWAL_NOT_TOLD_YET: "FEATURE_WITHDRAWAL_NOT_TOLD_YET",
|
|
1646
|
+
/**
|
|
1647
|
+
* The date a withdrawal would be lifted from lies before now: what was
|
|
1648
|
+
* withdrawn until then was withdrawn.
|
|
1649
|
+
*/
|
|
1650
|
+
FEATURE_WITHDRAWAL_LIFT_IN_PAST: "FEATURE_WITHDRAWAL_LIFT_IN_PAST",
|
|
1651
|
+
/**
|
|
1652
|
+
* The withdrawal does not take its feature away now — its date is ahead,
|
|
1653
|
+
* or it was lifted — so nothing ends at once because of it. Carries
|
|
1654
|
+
* `featureKey`.
|
|
1655
|
+
*/
|
|
1656
|
+
FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "FEATURE_WITHDRAWAL_NOT_IN_EFFECT",
|
|
1657
|
+
/**
|
|
1658
|
+
* The withdrawal did not reach the subscription, or the line to end: it
|
|
1659
|
+
* was not running at the announcement, or no longer holds the feature.
|
|
1660
|
+
*/
|
|
1661
|
+
FEATURE_WITHDRAWAL_DOES_NOT_REACH: "FEATURE_WITHDRAWAL_DOES_NOT_REACH",
|
|
1662
|
+
/** What was to be ended at once has ended already. */
|
|
1663
|
+
FEATURE_WITHDRAWAL_ALREADY_ENDED: "FEATURE_WITHDRAWAL_ALREADY_ENDED",
|
|
1664
|
+
/**
|
|
1665
|
+
* A cancellation is declared for a later date, and the installation's
|
|
1666
|
+
* subscription write or booking store cannot bring it forward (`endNow`).
|
|
1667
|
+
* It ends as declared. Carries that `date`.
|
|
1668
|
+
*/
|
|
1669
|
+
FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED: "FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED",
|
|
1330
1670
|
// ── the preview routes' own blockers and warnings ──
|
|
1331
1671
|
//
|
|
1332
1672
|
// These travel inside a 200 response, in `blockers[]` and `warnings[]`, not
|
|
@@ -1484,6 +1824,92 @@ var CONTRACT_ERROR_CODES = {
|
|
|
1484
1824
|
*/
|
|
1485
1825
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED"
|
|
1486
1826
|
};
|
|
1827
|
+
var INVOICE_ERROR_CODES = {
|
|
1828
|
+
/**
|
|
1829
|
+
* A charge already stands on an invoice: a second run issuing the same
|
|
1830
|
+
* charges lost the race. Nothing is written and no number is drawn.
|
|
1831
|
+
*/
|
|
1832
|
+
SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "SUBSCRIPTION_INVOICE_CHARGE_INVOICED",
|
|
1833
|
+
/**
|
|
1834
|
+
* The invoice lacks content the tax adapter requires of it — `missing`
|
|
1835
|
+
* names the fields, `adapter` the adapter. It draws no number and waits
|
|
1836
|
+
* until what is missing is supplied (`SC-PRIC-027`).
|
|
1837
|
+
*/
|
|
1838
|
+
SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE",
|
|
1839
|
+
/**
|
|
1840
|
+
* The contract's parties were copied by a migration and the operator has
|
|
1841
|
+
* not confirmed them yet; its charges are not invoiced until then
|
|
1842
|
+
* (`SC-AUD-012`).
|
|
1843
|
+
*/
|
|
1844
|
+
SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED: "SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED",
|
|
1845
|
+
/**
|
|
1846
|
+
* The subscriber's direct debit names no mandate reference
|
|
1847
|
+
* (`paymentMethodId`), which has to be announced before it is debited
|
|
1848
|
+
* (`SC-PRIC-028`). An invoice collected by it draws no number, and one
|
|
1849
|
+
* planned with it is not announced, until the payment method is replaced.
|
|
1850
|
+
*/
|
|
1851
|
+
SUBSCRIPTION_INVOICE_MANDATE_MISSING: "SUBSCRIPTION_INVOICE_MANDATE_MISSING",
|
|
1852
|
+
/**
|
|
1853
|
+
* The subscriber (`subscriberId`) has no payment method in use, so the
|
|
1854
|
+
* invoice is not collected until one is (`SC-PRIC-082`).
|
|
1855
|
+
*/
|
|
1856
|
+
SUBSCRIPTION_INVOICE_NO_PAYMENT_METHOD: "SUBSCRIPTION_INVOICE_NO_PAYMENT_METHOD",
|
|
1857
|
+
/**
|
|
1858
|
+
* A direct debit announced again after a failed collection (`attemptKey`)
|
|
1859
|
+
* waits until the subscriber has been told of that failure
|
|
1860
|
+
* (`SC-PRIC-080`).
|
|
1861
|
+
*/
|
|
1862
|
+
SUBSCRIPTION_INVOICE_FAILURE_NOT_TOLD: "SUBSCRIPTION_INVOICE_FAILURE_NOT_TOLD",
|
|
1863
|
+
/**
|
|
1864
|
+
* A file of the invoice's document has another checksum than the one
|
|
1865
|
+
* recorded or archived for it (`invoiceId`, `recorded`, `rendered`), and
|
|
1866
|
+
* the first stays. The run takes the archived file as the one issued, or —
|
|
1867
|
+
* where nothing is archived — records the new rendering in place of the
|
|
1868
|
+
* old (`SC-AUD-019`); the invoice waits only where another run changed the
|
|
1869
|
+
* record or the archive in between, and the next run settles it.
|
|
1870
|
+
*/
|
|
1871
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED: "SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED",
|
|
1872
|
+
/**
|
|
1873
|
+
* The tax adapter refuses the rendered document — `missing` names what it
|
|
1874
|
+
* lacks or disagrees with, `adapter` the adapter. The invoice keeps its
|
|
1875
|
+
* number and waits for a document that passes (`SC-PRIC-042`).
|
|
1876
|
+
*/
|
|
1877
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED: "SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED",
|
|
1878
|
+
/**
|
|
1879
|
+
* The archived document does not match the checksum recorded when it was
|
|
1880
|
+
* rendered (`invoiceId`). It is not handed out (`SC-AUD-019`).
|
|
1881
|
+
*/
|
|
1882
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH",
|
|
1883
|
+
/** The invoice's document is not archived yet (`invoiceId`); nothing is offered until it is (`SC-AUD-014`). */
|
|
1884
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED",
|
|
1885
|
+
/** No invoice with this id (`invoiceId`). */
|
|
1886
|
+
SUBSCRIPTION_INVOICE_NOT_FOUND: "SUBSCRIPTION_INVOICE_NOT_FOUND",
|
|
1887
|
+
/**
|
|
1888
|
+
* The subscriber's record holds no invoice email (`subscriberId`), so the
|
|
1889
|
+
* archived invoice cannot be sent (`SC-PRIC-047`); it is sent once one is
|
|
1890
|
+
* entered.
|
|
1891
|
+
*/
|
|
1892
|
+
SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL",
|
|
1893
|
+
/** No credit under this id for the tenant's subscriber (`creditId`). */
|
|
1894
|
+
SUBSCRIBER_CREDIT_NOT_FOUND: "SUBSCRIBER_CREDIT_NOT_FOUND",
|
|
1895
|
+
/**
|
|
1896
|
+
* A payout of more than the credit still owes (`creditId`, `open`): nothing
|
|
1897
|
+
* is paid out beyond it (`SC-PRIC-083`), and nothing is recorded.
|
|
1898
|
+
*/
|
|
1899
|
+
SUBSCRIBER_PAYOUT_EXCEEDS_CREDIT: "SUBSCRIBER_PAYOUT_EXCEEDS_CREDIT",
|
|
1900
|
+
/**
|
|
1901
|
+
* A payout on a day still to come (`paidOn`, `today` in the installation's
|
|
1902
|
+
* zone): a payout is recorded once it left.
|
|
1903
|
+
*/
|
|
1904
|
+
SUBSCRIBER_PAYOUT_NOT_MADE_YET: "SUBSCRIBER_PAYOUT_NOT_MADE_YET",
|
|
1905
|
+
/** No case to reconcile under this id (`caseId`). */
|
|
1906
|
+
SUBSCRIBER_RECONCILIATION_CASE_NOT_FOUND: "SUBSCRIBER_RECONCILIATION_CASE_NOT_FOUND",
|
|
1907
|
+
/**
|
|
1908
|
+
* A case closed before (`caseId`, `closedAt`), by the operator or by
|
|
1909
|
+
* SaaSiCat; its note stands, and nothing is written (`SC-ADM-033`).
|
|
1910
|
+
*/
|
|
1911
|
+
SUBSCRIBER_RECONCILIATION_CASE_CLOSED: "SUBSCRIBER_RECONCILIATION_CASE_CLOSED"
|
|
1912
|
+
};
|
|
1487
1913
|
var SUBSCRIBER_ERROR_CODES = {
|
|
1488
1914
|
/**
|
|
1489
1915
|
* A contract, a plan change or a booking for a tenant that has no
|
|
@@ -1664,6 +2090,7 @@ var PLATFORM_ERROR_CODES = {
|
|
|
1664
2090
|
...CATALOG_ERROR_CODES,
|
|
1665
2091
|
...BILLING_ERROR_CODES,
|
|
1666
2092
|
...CONTRACT_ERROR_CODES,
|
|
2093
|
+
...INVOICE_ERROR_CODES,
|
|
1667
2094
|
...SUBSCRIBER_ERROR_CODES,
|
|
1668
2095
|
...REGISTRATION_ERROR_CODES,
|
|
1669
2096
|
...PAYMENT_ERROR_CODES,
|
|
@@ -1717,7 +2144,45 @@ var OPTIONAL_CANONICAL_MODELS = {
|
|
|
1717
2144
|
SubscriptionNotice: ["tenantBilling.subscriptionNotices"],
|
|
1718
2145
|
VersionRetirement: ["tenantBilling.versionRetirements"],
|
|
1719
2146
|
BundleVersionRetirement: ["tenantBilling.bundleVersionRetirements"],
|
|
2147
|
+
FeatureWithdrawal: ["tenantBilling.featureWithdrawals"],
|
|
1720
2148
|
SubscriberLedgerEntry: ["entitlement.subscriberLedgerRepository"],
|
|
2149
|
+
SubscriptionInvoice: ["entitlement.subscriptionInvoiceRepository"],
|
|
2150
|
+
SubscriptionInvoiceLine: ["entitlement.subscriptionInvoiceRepository"],
|
|
2151
|
+
SubscriptionInvoiceNumber: ["entitlement.subscriptionInvoiceRepository"],
|
|
2152
|
+
SubscriptionInvoiceDocument: ["entitlement.subscriptionInvoiceRepository"],
|
|
2153
|
+
SubscriptionInvoiceArchive: ["entitlement.subscriptionInvoiceArchive"],
|
|
2154
|
+
SubscriptionInvoiceCollection: [
|
|
2155
|
+
"entitlement.invoiceCollectionRepository",
|
|
2156
|
+
"entitlement.subscriberPaymentRepository"
|
|
2157
|
+
],
|
|
2158
|
+
SubscriberPayment: [
|
|
2159
|
+
"entitlement.subscriberPaymentRepository",
|
|
2160
|
+
"entitlement.invoiceCollectionRepository"
|
|
2161
|
+
],
|
|
2162
|
+
SubscriptionInvoiceSettlement: [
|
|
2163
|
+
"entitlement.subscriberPaymentRepository",
|
|
2164
|
+
"entitlement.invoiceCollectionRepository"
|
|
2165
|
+
],
|
|
2166
|
+
SubscriberCredit: [
|
|
2167
|
+
"entitlement.subscriberPaymentRepository",
|
|
2168
|
+
"entitlement.invoiceCollectionRepository"
|
|
2169
|
+
],
|
|
2170
|
+
SubscriberCreditEntry: [
|
|
2171
|
+
"entitlement.subscriberPaymentRepository",
|
|
2172
|
+
"entitlement.invoiceCollectionRepository"
|
|
2173
|
+
],
|
|
2174
|
+
SubscriberRefund: [
|
|
2175
|
+
"entitlement.subscriberPaymentRepository",
|
|
2176
|
+
"entitlement.invoiceCollectionRepository"
|
|
2177
|
+
],
|
|
2178
|
+
SubscriberReversal: [
|
|
2179
|
+
"entitlement.subscriberPaymentRepository",
|
|
2180
|
+
"entitlement.invoiceCollectionRepository"
|
|
2181
|
+
],
|
|
2182
|
+
SubscriberReconciliationCase: [
|
|
2183
|
+
"entitlement.subscriberPaymentRepository",
|
|
2184
|
+
"entitlement.invoiceCollectionRepository"
|
|
2185
|
+
],
|
|
1721
2186
|
PromoCodeHold: ["promo.holdRepository"]
|
|
1722
2187
|
};
|
|
1723
2188
|
function membersLeftOut(notAdopted = []) {
|
|
@@ -1900,6 +2365,23 @@ var ERROR_MESSAGES_EN = {
|
|
|
1900
2365
|
BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "Version {version} of {bundleKey} is being retired, and this booking ends before it would move. Your subscription ends by then as well.",
|
|
1901
2366
|
BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "{bundleName} moves to its new version on {date}, and your plan changes before then, so the price a switch would keep is not known yet. Switch once your plan has changed, or let it move on that date.",
|
|
1902
2367
|
BUNDLE_VERSION_OFFER_CHANGED: "The offer changed since it was shown. Look at the current one before switching.",
|
|
2368
|
+
FEATURE_WITHDRAWAL_UNAVAILABLE: "Withdrawing a feature is not offered here: the stores cannot list everybody a withdrawal reaches.",
|
|
2369
|
+
FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "The catalogue knows no feature {featureKey}.",
|
|
2370
|
+
FEATURE_WITHDRAWAL_DATE_IN_PAST: "A feature is withdrawn from now on or from a later date, never from a date already past.",
|
|
2371
|
+
FEATURE_WITHDRAWAL_OPEN: "{featureKey} is withdrawn already, and that withdrawal is not lifted. Lift it before announcing another.",
|
|
2372
|
+
FEATURE_WITHDRAWAL_OVERLAPS: "{featureKey} is withdrawn until {date}. Another withdrawal can begin on that date at the earliest.",
|
|
2373
|
+
FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "The reduction for {key} billed {billingCycle} names nothing this withdrawal reaches.",
|
|
2374
|
+
FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "The reduction for {key} billed {billingCycle} is named twice.",
|
|
2375
|
+
FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "The reduction for {key} billed {billingCycle} is more than the lowest price it reduces, {price}.",
|
|
2376
|
+
FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "The subscriptions this withdrawal reaches changed since they were shown. Look at them again before announcing it.",
|
|
2377
|
+
FEATURE_WITHDRAWAL_NOT_FOUND: "Feature withdrawal {withdrawalId} not found.",
|
|
2378
|
+
FEATURE_WITHDRAWAL_ALREADY_LIFTED: "This withdrawal is lifted already, from {date}.",
|
|
2379
|
+
FEATURE_WITHDRAWAL_NOT_TOLD_YET: "Nobody has been told of this withdrawal yet, and it is not lifted before they are. Try again in a moment.",
|
|
2380
|
+
FEATURE_WITHDRAWAL_LIFT_IN_PAST: "A withdrawal is lifted from now on or from a later date, never from a date already past.",
|
|
2381
|
+
FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "{featureKey} is not withdrawn now, so nothing can be ended at once because of it.",
|
|
2382
|
+
FEATURE_WITHDRAWAL_DOES_NOT_REACH: "This withdrawal does not reach your subscription, or no longer this booking.",
|
|
2383
|
+
FEATURE_WITHDRAWAL_ALREADY_ENDED: "This has ended already.",
|
|
2384
|
+
FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED: "Your cancellation for {date} is recorded, and it cannot be brought forward here. It ends on that date, and until then the reduction applies.",
|
|
1903
2385
|
RETIREMENT_SWITCH_NOT_PENDING: "No retirement of your version is waiting for its date, so there is nothing to switch to.",
|
|
1904
2386
|
RETIREMENT_SWITCH_IN_TRIAL: "The switch opens when your trial ends.",
|
|
1905
2387
|
RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "{bundleName} cannot run on {planName} and runs until {until} at the earliest. Once it is cancelled, the switch can be made from that day.",
|
|
@@ -1942,6 +2424,23 @@ var ERROR_MESSAGES_EN = {
|
|
|
1942
2424
|
PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions cannot be lowered.",
|
|
1943
2425
|
// ── contract ──
|
|
1944
2426
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "The contract states {stated} % in {field}, and the tax adapter decides {decided} %.",
|
|
2427
|
+
SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "Charge '{chargeId}' already stands on an invoice.",
|
|
2428
|
+
SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "The invoice lacks what the tax adapter {adapter} requires of it: {missing}. It is issued once that is supplied.",
|
|
2429
|
+
SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED: "Contract {contractId} names no confirmed issuer and subscriber \u2014 a migration copied its parties, or it names no issuer \u2014 so its charges are not invoiced.",
|
|
2430
|
+
SUBSCRIPTION_INVOICE_MANDATE_MISSING: "The direct debit {paymentMethodId} names no mandate reference, which has to be announced before it is debited. The invoice waits until the payment method is replaced.",
|
|
2431
|
+
SUBSCRIPTION_INVOICE_NO_PAYMENT_METHOD: "The subscriber {subscriberId} has no payment method in use. The invoice is collected once one is.",
|
|
2432
|
+
SUBSCRIPTION_INVOICE_FAILURE_NOT_TOLD: "The direct debit is announced once the subscriber has been told that collection {attemptKey} failed.",
|
|
2433
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED: "The document of invoice {invoiceId} differs from the one recorded or archived for it ({rendered} instead of {recorded}); the first stays, and the next run settles which one the invoice keeps.",
|
|
2434
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED: "The tax adapter {adapter} refuses the rendered invoice: {missing}. The invoice keeps its number and waits for a document that passes.",
|
|
2435
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "The archived document of invoice {invoiceId} does not match the checksum recorded when it was rendered, and is not handed out.",
|
|
2436
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "The document of invoice {invoiceId} is not archived yet.",
|
|
2437
|
+
SUBSCRIPTION_INVOICE_NOT_FOUND: "No invoice '{invoiceId}'.",
|
|
2438
|
+
SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "Subscriber {subscriberId} has no invoice email, so its invoice cannot be sent; it is sent once one is entered.",
|
|
2439
|
+
SUBSCRIBER_CREDIT_NOT_FOUND: "No credit '{creditId}' of this tenant's subscriber.",
|
|
2440
|
+
SUBSCRIBER_PAYOUT_EXCEEDS_CREDIT: "The credit {creditId} still owes {open}; nothing beyond that is paid out, and nothing was recorded.",
|
|
2441
|
+
SUBSCRIBER_PAYOUT_NOT_MADE_YET: "A payout is recorded once it left: {paidOn} is after today, {today}.",
|
|
2442
|
+
SUBSCRIBER_RECONCILIATION_CASE_NOT_FOUND: "No case to reconcile '{caseId}'.",
|
|
2443
|
+
SUBSCRIBER_RECONCILIATION_CASE_CLOSED: "The case {caseId} was closed on {closedAt}; its note stands, and nothing was recorded.",
|
|
1945
2444
|
CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "A checkout offer can yield only one contract, and only once its line items are frozen.",
|
|
1946
2445
|
CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "A checkout offer requires a frozen plan line item.",
|
|
1947
2446
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Every selected bundle version requires a frozen bundle line item.",
|
|
@@ -2027,11 +2526,21 @@ var ERROR_MESSAGES_EN = {
|
|
|
2027
2526
|
TAX_VAT_ID_CHECK_NOT_COMPLETED: "The VAT identification number could not be checked just now ({reason}). Please try again later.",
|
|
2028
2527
|
TAX_VAT_ID_CHECK_NOT_AVAILABLE: "This installation names no tax adapter, so no VAT identification number is checked."
|
|
2029
2528
|
};
|
|
2529
|
+
function parameterText(value) {
|
|
2530
|
+
if (typeof value === "string") return value;
|
|
2531
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
2532
|
+
return String(value);
|
|
2533
|
+
}
|
|
2534
|
+
if (value instanceof Date) return String(value);
|
|
2535
|
+
if (!Array.isArray(value)) return null;
|
|
2536
|
+
const items = value.map(parameterText);
|
|
2537
|
+
return items.every((item) => item !== null) ? items.join(",") : null;
|
|
2538
|
+
}
|
|
2030
2539
|
function formatErrorMessage(template, params = {}) {
|
|
2031
|
-
return template.replace(
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2540
|
+
return template.replace(
|
|
2541
|
+
/\{(\w+)\}/g,
|
|
2542
|
+
(match, name) => parameterText(params[name]) ?? match
|
|
2543
|
+
);
|
|
2035
2544
|
}
|
|
2036
2545
|
function resolveErrorMessage(body, overrides = {}, defaults = ERROR_MESSAGES_EN) {
|
|
2037
2546
|
const code = typeof body.code === "string" ? body.code : void 0;
|
|
@@ -2209,6 +2718,13 @@ function selectChargeableBundles(planFeatures, selectedBundles) {
|
|
|
2209
2718
|
}
|
|
2210
2719
|
|
|
2211
2720
|
// src/payment-gateway.types.ts
|
|
2721
|
+
var RETRYABLE_FAILURES = /* @__PURE__ */ new Set([
|
|
2722
|
+
"insufficient-funds",
|
|
2723
|
+
"declined"
|
|
2724
|
+
]);
|
|
2725
|
+
function isRetryableCollectionFailure(reason) {
|
|
2726
|
+
return RETRYABLE_FAILURES.has(reason);
|
|
2727
|
+
}
|
|
2212
2728
|
var PAYMENT_CALLBACK_REJECTED = "PAYMENT_CALLBACK_REJECTED";
|
|
2213
2729
|
var PaymentCallbackRejectedError = class extends Error {
|
|
2214
2730
|
code = PAYMENT_CALLBACK_REJECTED;
|
|
@@ -2384,17 +2900,20 @@ var ORIGINS = {
|
|
|
2384
2900
|
bundleBooking: true,
|
|
2385
2901
|
bundleChange: true,
|
|
2386
2902
|
credit: true,
|
|
2387
|
-
correction: true
|
|
2903
|
+
correction: true,
|
|
2904
|
+
reductionTakenBack: true
|
|
2388
2905
|
};
|
|
2389
2906
|
var SOURCES = {
|
|
2390
2907
|
plan: true,
|
|
2391
2908
|
bundle: true,
|
|
2392
2909
|
discount: true
|
|
2393
2910
|
};
|
|
2394
|
-
var
|
|
2911
|
+
var SUBSCRIBER_CHARGE_ORIGINS = keysOf(ORIGINS);
|
|
2912
|
+
var SUBSCRIBER_CHARGE_SOURCES = keysOf(SOURCES);
|
|
2913
|
+
var TABLE3 = "subscriber_ledger_entries";
|
|
2395
2914
|
var CENT_TOLERANCE = 1e-6;
|
|
2396
2915
|
function toSubscriberChargeRecord(row) {
|
|
2397
|
-
const where = (column) => ({ table:
|
|
2916
|
+
const where = (column) => ({ table: TABLE3, column, id: row.id });
|
|
2398
2917
|
return {
|
|
2399
2918
|
id: row.id,
|
|
2400
2919
|
subscriberId: row.subscriberId,
|
|
@@ -2402,8 +2921,8 @@ function toSubscriberChargeRecord(row) {
|
|
|
2402
2921
|
subscriptionId: row.subscriptionId,
|
|
2403
2922
|
contractId: row.contractId,
|
|
2404
2923
|
contractLineItemId: row.contractLineItemId,
|
|
2405
|
-
origin: oneOf(
|
|
2406
|
-
source: oneOf(
|
|
2924
|
+
origin: oneOf(SUBSCRIBER_CHARGE_ORIGINS, row.origin, where("origin")),
|
|
2925
|
+
source: oneOf(SUBSCRIBER_CHARGE_SOURCES, row.source, where("source")),
|
|
2407
2926
|
sourceRef: row.sourceRef,
|
|
2408
2927
|
periodStart: row.periodStart,
|
|
2409
2928
|
periodEnd: row.periodEnd,
|
|
@@ -2443,6 +2962,96 @@ function keysOf(record) {
|
|
|
2443
2962
|
// src/subscriber-ledger.types.ts
|
|
2444
2963
|
var SUBSCRIBER_ACCOUNT_CAPABILITY = "charges.read";
|
|
2445
2964
|
|
|
2965
|
+
// src/subscription-invoice.types.ts
|
|
2966
|
+
var INVOICE_SEQUENCE_DIGITS = 6;
|
|
2967
|
+
function formatInvoiceNumber(prefix, year, sequence) {
|
|
2968
|
+
if (!Number.isInteger(sequence) || sequence < 1) {
|
|
2969
|
+
throw new RangeError(`An invoice number's sequence starts at 1; ${sequence} is not one.`);
|
|
2970
|
+
}
|
|
2971
|
+
return `${prefix}-${year}-${String(sequence).padStart(INVOICE_SEQUENCE_DIGITS, "0")}`;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// src/column-day.ts
|
|
2975
|
+
var DAY_LENGTH = "YYYY-MM-DD".length;
|
|
2976
|
+
function dayOf2(value, where) {
|
|
2977
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
2978
|
+
return value.toISOString().slice(0, DAY_LENGTH);
|
|
2979
|
+
}
|
|
2980
|
+
if (typeof value === "string" && value.length === DAY_LENGTH && !Number.isNaN(Date.parse(value))) {
|
|
2981
|
+
return value;
|
|
2982
|
+
}
|
|
2983
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a day.`);
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
// src/subscriber-payment-method-mapping.ts
|
|
2987
|
+
var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
|
|
2988
|
+
var STATUSES = ["ACTIVE", "REPLACED"];
|
|
2989
|
+
var TABLE4 = "subscriber_payment_methods";
|
|
2990
|
+
function toSubscriberPaymentMethodRecord(row) {
|
|
2991
|
+
return {
|
|
2992
|
+
id: row.id,
|
|
2993
|
+
subscriberId: row.subscriberId,
|
|
2994
|
+
gatewayAccount: row.gatewayAccount,
|
|
2995
|
+
provider: row.provider,
|
|
2996
|
+
customerRef: row.customerRef,
|
|
2997
|
+
paymentMethodRef: row.paymentMethodRef,
|
|
2998
|
+
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE4, column: "type", id: row.id }),
|
|
2999
|
+
brand: row.brand,
|
|
3000
|
+
last4: row.last4,
|
|
3001
|
+
expiryMonth: row.expiryMonth,
|
|
3002
|
+
expiryYear: row.expiryYear,
|
|
3003
|
+
country: row.country,
|
|
3004
|
+
bankCode: row.bankCode,
|
|
3005
|
+
mandateReference: row.mandateReference,
|
|
3006
|
+
status: oneOf(STATUSES, row.status, { table: TABLE4, column: "status", id: row.id }),
|
|
3007
|
+
confirmedAt: row.confirmedAt,
|
|
3008
|
+
replacedAt: row.replacedAt,
|
|
3009
|
+
createdAt: row.createdAt
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
function subscriberPaymentMethodColumns(data) {
|
|
3013
|
+
return {
|
|
3014
|
+
subscriberId: data.subscriberId,
|
|
3015
|
+
gatewayAccount: data.gatewayAccount,
|
|
3016
|
+
provider: data.provider,
|
|
3017
|
+
customerRef: data.customerRef,
|
|
3018
|
+
paymentMethodRef: data.paymentMethodRef,
|
|
3019
|
+
type: data.type,
|
|
3020
|
+
brand: data.brand,
|
|
3021
|
+
last4: data.last4,
|
|
3022
|
+
expiryMonth: data.expiryMonth,
|
|
3023
|
+
expiryYear: data.expiryYear,
|
|
3024
|
+
country: data.country,
|
|
3025
|
+
bankCode: data.bankCode,
|
|
3026
|
+
mandateReference: data.mandateReference,
|
|
3027
|
+
confirmedAt: data.confirmedAt
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
var FOREIGN_PAYMENT_METHOD_REFERENCE = "FOREIGN_PAYMENT_METHOD_REFERENCE";
|
|
3031
|
+
var ForeignPaymentMethodReferenceError = class extends Error {
|
|
3032
|
+
constructor(gatewayAccount, paymentMethodRef) {
|
|
3033
|
+
super(
|
|
3034
|
+
`Payment method '${paymentMethodRef}' of account '${gatewayAccount}' belongs to another subscriber. A reference belongs to exactly one, so it is not handed out.`
|
|
3035
|
+
);
|
|
3036
|
+
this.gatewayAccount = gatewayAccount;
|
|
3037
|
+
this.paymentMethodRef = paymentMethodRef;
|
|
3038
|
+
this.name = "ForeignPaymentMethodReferenceError";
|
|
3039
|
+
}
|
|
3040
|
+
gatewayAccount;
|
|
3041
|
+
paymentMethodRef;
|
|
3042
|
+
code = FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
3043
|
+
};
|
|
3044
|
+
function isForeignPaymentMethodReferenceError(error) {
|
|
3045
|
+
return error instanceof Error && error.code === FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
3046
|
+
}
|
|
3047
|
+
function refuseForeignPaymentMethodReference(recorded, subscriberId) {
|
|
3048
|
+
if (recorded.subscriberId === subscriberId) return;
|
|
3049
|
+
throw new ForeignPaymentMethodReferenceError(
|
|
3050
|
+
recorded.gatewayAccount,
|
|
3051
|
+
recorded.paymentMethodRef
|
|
3052
|
+
);
|
|
3053
|
+
}
|
|
3054
|
+
|
|
2446
3055
|
// src/subscriber.types.ts
|
|
2447
3056
|
var SUBSCRIBER_INVOICE_ADDRESS_FIELDS = [
|
|
2448
3057
|
"addressLine1",
|
|
@@ -2574,89 +3183,1375 @@ function textEntries(value) {
|
|
|
2574
3183
|
return entries;
|
|
2575
3184
|
}
|
|
2576
3185
|
|
|
2577
|
-
// src/
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
function
|
|
3186
|
+
// src/subscription-invoice-mapping.ts
|
|
3187
|
+
var INVOICES = "subscription_invoices";
|
|
3188
|
+
var LINES = "subscription_invoice_lines";
|
|
3189
|
+
var CENT_TOLERANCE2 = 1e-6;
|
|
3190
|
+
function toSubscriptionInvoiceRecord(row, lines) {
|
|
3191
|
+
const where = (column) => ({ table: INVOICES, column, id: row.id });
|
|
3192
|
+
const treatment = toTaxTreatment(row.taxTreatment);
|
|
3193
|
+
if (!treatment) {
|
|
3194
|
+
throw new Error(`${INVOICES}.taxTreatment of ${row.id} is not a tax treatment.`);
|
|
3195
|
+
}
|
|
3196
|
+
const dueDate = dayOf2(row.dueDate, where("dueDate"));
|
|
2582
3197
|
return {
|
|
2583
3198
|
id: row.id,
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
3199
|
+
number: row.number,
|
|
3200
|
+
numberPrefix: row.numberPrefix,
|
|
3201
|
+
numberYear: row.numberYear,
|
|
3202
|
+
numberSequence: row.numberSequence,
|
|
3203
|
+
tenantId: row.tenantId,
|
|
3204
|
+
subscriberId: row.subscriberId,
|
|
3205
|
+
subscriptionId: row.subscriptionId,
|
|
3206
|
+
contractId: row.contractId,
|
|
3207
|
+
issuedAt: row.issuedAt,
|
|
3208
|
+
issueDate: dayOf2(row.issueDate, where("issueDate")),
|
|
3209
|
+
dueDate,
|
|
3210
|
+
servicePeriodFrom: dayOf2(row.servicePeriodFrom, where("servicePeriodFrom")),
|
|
3211
|
+
servicePeriodUntil: dayOf2(row.servicePeriodUntil, where("servicePeriodUntil")),
|
|
3212
|
+
...collectionOf(row.payment, row.collectionDate, dueDate, where),
|
|
3213
|
+
currency: row.currency,
|
|
3214
|
+
issuer: issuerOf(row.issuerParty, where("issuerParty")),
|
|
3215
|
+
subscriber: subscriberOf(row.subscriberParty, where("subscriberParty")),
|
|
3216
|
+
footer: footerOf(row.footer, where("footer")),
|
|
3217
|
+
taxTreatment: treatment,
|
|
3218
|
+
taxes: taxesOf(row.taxes, where("taxes")),
|
|
3219
|
+
totalNet: amountOf2(row.totalNet, where("totalNet")),
|
|
3220
|
+
totalTax: amountOf2(row.totalTax, where("totalTax")),
|
|
3221
|
+
totalGross: amountOf2(row.totalGross, where("totalGross")),
|
|
3222
|
+
lines: [...lines].sort((a, b) => a.position - b.position).map((line) => toSubscriptionInvoiceLineRecord(line)),
|
|
3223
|
+
createdAt: row.createdAt
|
|
2599
3224
|
};
|
|
2600
3225
|
}
|
|
2601
|
-
function
|
|
3226
|
+
function toSubscriptionInvoiceLineRecord(row) {
|
|
3227
|
+
const where = (column) => ({ table: LINES, column, id: row.id });
|
|
2602
3228
|
return {
|
|
2603
3229
|
id: row.id,
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
3230
|
+
invoiceId: row.invoiceId,
|
|
3231
|
+
position: row.position,
|
|
3232
|
+
chargeId: row.chargeId,
|
|
3233
|
+
contractLineItemId: row.contractLineItemId,
|
|
3234
|
+
title: row.title,
|
|
3235
|
+
origin: oneOf(SUBSCRIBER_CHARGE_ORIGINS, row.origin, where("origin")),
|
|
3236
|
+
source: oneOf(SUBSCRIBER_CHARGE_SOURCES, row.source, where("source")),
|
|
3237
|
+
periodFrom: dayOf2(row.periodFrom, where("periodFrom")),
|
|
3238
|
+
periodUntil: dayOf2(row.periodUntil, where("periodUntil")),
|
|
3239
|
+
amountNet: amountOf2(row.amountNet, where("amountNet")),
|
|
3240
|
+
taxRate: amountOf2(row.taxRate, where("taxRate"))
|
|
2610
3241
|
};
|
|
2611
3242
|
}
|
|
2612
|
-
function
|
|
3243
|
+
function subscriptionInvoiceColumns(invoice, drawn) {
|
|
2613
3244
|
return {
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3245
|
+
number: drawn.number,
|
|
3246
|
+
numberPrefix: invoice.numberPrefix,
|
|
3247
|
+
numberYear: invoice.numberYear,
|
|
3248
|
+
numberSequence: drawn.numberSequence,
|
|
3249
|
+
tenantId: invoice.tenantId,
|
|
3250
|
+
subscriberId: invoice.subscriberId,
|
|
3251
|
+
subscriptionId: invoice.subscriptionId,
|
|
3252
|
+
contractId: invoice.contractId,
|
|
3253
|
+
issuedAt: invoice.issuedAt,
|
|
3254
|
+
issueDate: invoice.issueDate,
|
|
3255
|
+
dueDate: invoice.dueDate,
|
|
3256
|
+
servicePeriodFrom: invoice.servicePeriodFrom,
|
|
3257
|
+
servicePeriodUntil: invoice.servicePeriodUntil,
|
|
3258
|
+
payment: invoice.payment ? { ...invoice.payment } : null,
|
|
3259
|
+
collectionDate: invoice.collectionDate,
|
|
3260
|
+
currency: invoice.currency,
|
|
3261
|
+
issuerParty: { ...invoice.issuer },
|
|
3262
|
+
subscriberParty: { ...invoice.subscriber },
|
|
3263
|
+
footer: [...invoice.footer],
|
|
3264
|
+
taxTreatment: taxTreatmentToJson(invoice.taxTreatment),
|
|
3265
|
+
taxes: invoice.taxes.map((entry) => ({ rate: entry.rate, net: entry.net, tax: entry.tax })),
|
|
3266
|
+
totalNet: centsText(invoice.totalNet, "totalNet"),
|
|
3267
|
+
totalTax: centsText(invoice.totalTax, "totalTax"),
|
|
3268
|
+
totalGross: centsText(invoice.totalGross, "totalGross")
|
|
2621
3269
|
};
|
|
2622
3270
|
}
|
|
2623
|
-
function
|
|
2624
|
-
const previous = {};
|
|
2625
|
-
const next = {};
|
|
2626
|
-
for (const field of LEGAL_IDENTITY_FIELDS) {
|
|
2627
|
-
const value = corrected[field];
|
|
2628
|
-
if (value === void 0 || value === current[field]) continue;
|
|
2629
|
-
previous[field] = current[field];
|
|
2630
|
-
next[field] = value;
|
|
2631
|
-
}
|
|
3271
|
+
function subscriptionInvoiceLineColumns(line, invoiceId) {
|
|
2632
3272
|
return {
|
|
2633
|
-
|
|
2634
|
-
|
|
3273
|
+
invoiceId,
|
|
3274
|
+
position: line.position,
|
|
3275
|
+
chargeId: line.chargeId,
|
|
3276
|
+
contractLineItemId: line.contractLineItemId,
|
|
3277
|
+
title: line.title,
|
|
3278
|
+
origin: line.origin,
|
|
3279
|
+
source: line.source,
|
|
3280
|
+
periodFrom: line.periodFrom,
|
|
3281
|
+
periodUntil: line.periodUntil,
|
|
3282
|
+
amountNet: centsText(line.amountNet, "amountNet"),
|
|
3283
|
+
taxRate: centsText(line.taxRate, "taxRate")
|
|
2635
3284
|
};
|
|
2636
3285
|
}
|
|
2637
|
-
function
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
3286
|
+
function utcMidnightOf(day) {
|
|
3287
|
+
return /* @__PURE__ */ new Date(`${day}T00:00:00.000Z`);
|
|
3288
|
+
}
|
|
3289
|
+
function collectionOf(payment, collectionDate, dueDate, where) {
|
|
3290
|
+
const paymentAt = where("payment");
|
|
3291
|
+
if (payment === void 0 || collectionDate === void 0) {
|
|
3292
|
+
throw new Error(
|
|
3293
|
+
`${paymentAt.table} row ${paymentAt.id} was read without its payment or its collection date.`
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
3296
|
+
if (payment === null) {
|
|
3297
|
+
if (collectionDate !== null) {
|
|
3298
|
+
throw new Error(
|
|
3299
|
+
`${paymentAt.table}.collectionDate of ${paymentAt.id} is set on an invoice that is not collected.`
|
|
3300
|
+
);
|
|
3301
|
+
}
|
|
3302
|
+
return { payment: null, collectionDate: null };
|
|
3303
|
+
}
|
|
3304
|
+
if (typeof payment !== "object" || Array.isArray(payment)) {
|
|
3305
|
+
throw new Error(`${paymentAt.table}.payment of ${paymentAt.id} is not a payment method.`);
|
|
3306
|
+
}
|
|
3307
|
+
const required = (key) => {
|
|
3308
|
+
const value = textOrNull2(payment, key, paymentAt);
|
|
3309
|
+
if (value === null) {
|
|
3310
|
+
throw new Error(`${paymentAt.table}.payment of ${paymentAt.id} names no ${key}.`);
|
|
3311
|
+
}
|
|
3312
|
+
return value;
|
|
2648
3313
|
};
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
3314
|
+
const type = oneOf(PAYMENT_METHOD_TYPES, required("type"), {
|
|
3315
|
+
...paymentAt,
|
|
3316
|
+
column: "payment.type"
|
|
3317
|
+
});
|
|
3318
|
+
const recorded = {
|
|
3319
|
+
paymentMethodId: required("paymentMethodId"),
|
|
3320
|
+
gatewayAccount: required("gatewayAccount"),
|
|
3321
|
+
type,
|
|
3322
|
+
brand: textOrNull2(payment, "brand", paymentAt),
|
|
3323
|
+
last4: required("last4"),
|
|
3324
|
+
mandateReference: type === "sepa_debit" ? required("mandateReference") : null
|
|
2653
3325
|
};
|
|
3326
|
+
if (type !== "sepa_debit") {
|
|
3327
|
+
if (collectionDate !== null) {
|
|
3328
|
+
throw new Error(
|
|
3329
|
+
`${paymentAt.table}.collectionDate of ${paymentAt.id} is set on an invoice paid by ${type}.`
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
return { payment: recorded, collectionDate: null };
|
|
3333
|
+
}
|
|
3334
|
+
const collected = dayOf2(collectionDate, where("collectionDate"));
|
|
3335
|
+
if (collected !== dueDate) {
|
|
3336
|
+
throw new Error(
|
|
3337
|
+
`${paymentAt.table}.dueDate of ${paymentAt.id} is ${dueDate}, and a direct debit falls due on the day it is collected, ${collected}.`
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3340
|
+
return { payment: recorded, collectionDate: collected };
|
|
2654
3341
|
}
|
|
2655
|
-
function
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
3342
|
+
function footerOf(value, where) {
|
|
3343
|
+
if (!Array.isArray(value) || !value.every((line) => typeof line === "string")) {
|
|
3344
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a list of lines.`);
|
|
3345
|
+
}
|
|
3346
|
+
return [...value];
|
|
3347
|
+
}
|
|
3348
|
+
function amountOf2(value, where) {
|
|
3349
|
+
const amount = Number(value === null || value === void 0 ? NaN : String(value));
|
|
3350
|
+
if (!Number.isFinite(amount)) {
|
|
3351
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not an amount.`);
|
|
3352
|
+
}
|
|
3353
|
+
return amount;
|
|
3354
|
+
}
|
|
3355
|
+
function centsText(amount, field) {
|
|
3356
|
+
const cents2 = amount * 100;
|
|
3357
|
+
if (!Number.isFinite(cents2) || Math.abs(cents2 - Math.round(cents2)) > CENT_TOLERANCE2) {
|
|
3358
|
+
throw new Error(
|
|
3359
|
+
`An invoice's ${field} of ${amount} is not a whole number of cents; it is rounded once, where the invoice is drafted, before it is written.`
|
|
3360
|
+
);
|
|
3361
|
+
}
|
|
3362
|
+
return amount.toFixed(2);
|
|
3363
|
+
}
|
|
3364
|
+
function fieldOf(source, key) {
|
|
3365
|
+
const field = Object.getOwnPropertyDescriptor(source, key)?.value;
|
|
3366
|
+
return field;
|
|
3367
|
+
}
|
|
3368
|
+
function textOrNull2(source, key, where) {
|
|
3369
|
+
const value = fieldOf(source, key);
|
|
3370
|
+
if (value === null || value === void 0) return null;
|
|
3371
|
+
if (typeof value !== "string") {
|
|
3372
|
+
throw new Error(`${where.table}.${where.column}.${key} of ${where.id} is not text.`);
|
|
3373
|
+
}
|
|
3374
|
+
return value;
|
|
3375
|
+
}
|
|
3376
|
+
function partyOf(value, where) {
|
|
3377
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3378
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a party.`);
|
|
3379
|
+
}
|
|
3380
|
+
const source = value;
|
|
3381
|
+
const legalName = textOrNull2(source, "legalName", where);
|
|
3382
|
+
if (legalName === null) {
|
|
3383
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} names no legal name.`);
|
|
3384
|
+
}
|
|
3385
|
+
return {
|
|
3386
|
+
legalName,
|
|
3387
|
+
vatId: textOrNull2(source, "vatId", where),
|
|
3388
|
+
taxNumber: textOrNull2(source, "taxNumber", where),
|
|
3389
|
+
addressLine1: textOrNull2(source, "addressLine1", where),
|
|
3390
|
+
addressLine2: textOrNull2(source, "addressLine2", where),
|
|
3391
|
+
postalCode: textOrNull2(source, "postalCode", where),
|
|
3392
|
+
city: textOrNull2(source, "city", where),
|
|
3393
|
+
country: textOrNull2(source, "country", where)
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
function issuerOf(value, where) {
|
|
3397
|
+
return partyOf(value, where);
|
|
3398
|
+
}
|
|
3399
|
+
function subscriberOf(value, where) {
|
|
3400
|
+
const party = partyOf(value, where);
|
|
3401
|
+
const customerNumber = textOrNull2(value, "customerNumber", where);
|
|
3402
|
+
if (customerNumber === null) {
|
|
3403
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} names no customer number.`);
|
|
3404
|
+
}
|
|
3405
|
+
return { ...party, customerNumber };
|
|
3406
|
+
}
|
|
3407
|
+
function taxesOf(value, where) {
|
|
3408
|
+
if (!Array.isArray(value)) {
|
|
3409
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not a list of rates.`);
|
|
3410
|
+
}
|
|
3411
|
+
return value.map((entry) => {
|
|
3412
|
+
const item = entry ?? {};
|
|
3413
|
+
return {
|
|
3414
|
+
rate: amountOf2(item.rate, where),
|
|
3415
|
+
net: amountOf2(item.net, where),
|
|
3416
|
+
tax: amountOf2(item.tax, where)
|
|
3417
|
+
};
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
// src/subscription-invoice-refusals.ts
|
|
3422
|
+
function subscriptionInvoiceChargeInvoiced(chargeId) {
|
|
3423
|
+
return new PersistenceRefusal(
|
|
3424
|
+
INVOICE_ERROR_CODES.SUBSCRIPTION_INVOICE_CHARGE_INVOICED,
|
|
3425
|
+
"moved",
|
|
3426
|
+
`Charge '${chargeId}' already stands on an invoice.`,
|
|
3427
|
+
{ chargeId }
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
function subscriptionInvoiceDocumentChanged(invoiceId, recorded, rendered) {
|
|
3431
|
+
return new PersistenceRefusal(
|
|
3432
|
+
INVOICE_ERROR_CODES.SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED,
|
|
3433
|
+
"moved",
|
|
3434
|
+
`The document rendered for invoice '${invoiceId}' differs from the one recorded for it.`,
|
|
3435
|
+
{ invoiceId, recorded, rendered }
|
|
3436
|
+
);
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
// src/subscription-invoice-draft.ts
|
|
3440
|
+
function invoiceGroupsOf(charges) {
|
|
3441
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3442
|
+
for (const charge of charges) {
|
|
3443
|
+
const key = `${charge.contractId}|${charge.bookedAt.toISOString()}`;
|
|
3444
|
+
const group = groups.get(key);
|
|
3445
|
+
if (group) group.charges.push(charge);
|
|
3446
|
+
else
|
|
3447
|
+
groups.set(key, {
|
|
3448
|
+
contractId: charge.contractId,
|
|
3449
|
+
bookedAt: charge.bookedAt,
|
|
3450
|
+
charges: [charge]
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
return [...groups.values()].filter((group) => group.charges.some((charge) => charge.amountNet !== 0)).sort(
|
|
3454
|
+
(a, b) => a.bookedAt.getTime() - b.bookedAt.getTime() || a.contractId.localeCompare(b.contractId)
|
|
3455
|
+
);
|
|
3456
|
+
}
|
|
3457
|
+
var DIRECT_DEBIT_LEAD_DAYS = 14;
|
|
3458
|
+
function directDebitLeadDaysOf(invoicing) {
|
|
3459
|
+
return invoicing.directDebitLeadDays ?? DIRECT_DEBIT_LEAD_DAYS;
|
|
3460
|
+
}
|
|
3461
|
+
function invoicePaymentOf(method) {
|
|
3462
|
+
if (!method) return null;
|
|
3463
|
+
if (method.type === "sepa_debit" && method.mandateReference === null) return null;
|
|
3464
|
+
return {
|
|
3465
|
+
paymentMethodId: method.id,
|
|
3466
|
+
gatewayAccount: method.gatewayAccount,
|
|
3467
|
+
type: method.type,
|
|
3468
|
+
brand: method.brand,
|
|
3469
|
+
last4: method.last4,
|
|
3470
|
+
mandateReference: method.type === "sepa_debit" ? method.mandateReference : null
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
function draftSubscriptionInvoice(input) {
|
|
3474
|
+
const { group, treatment, timeZone } = input;
|
|
3475
|
+
const lines = linesOf(input);
|
|
3476
|
+
const issueDate = dayInZone(input.issuedAt, timeZone);
|
|
3477
|
+
const tax = input.tax(lines.map((line) => ({ net: line.amountNet, rate: line.taxRate })));
|
|
3478
|
+
const payment = tax.gross > 0 ? input.payment : null;
|
|
3479
|
+
if (payment?.type === "sepa_debit" && payment.mandateReference === null) {
|
|
3480
|
+
throw new Error(
|
|
3481
|
+
`Payment method ${payment.paymentMethodId} is a direct debit without a mandate reference, which an invoice collected by it has to announce.`
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
const collectionDate = payment?.type === "sepa_debit" ? addDaysToDay(issueDate, input.directDebitLeadDays) : null;
|
|
3485
|
+
return {
|
|
3486
|
+
numberPrefix: input.numberPrefix,
|
|
3487
|
+
numberYear: yearOfDay(issueDate),
|
|
3488
|
+
tenantId: input.tenantId,
|
|
3489
|
+
subscriberId: input.subscriberId,
|
|
3490
|
+
subscriptionId: group.charges[0].subscriptionId,
|
|
3491
|
+
contractId: group.contractId,
|
|
3492
|
+
issuedAt: input.issuedAt,
|
|
3493
|
+
issueDate,
|
|
3494
|
+
dueDate: collectionDate ?? addDaysToDay(issueDate, input.paymentTermDays),
|
|
3495
|
+
payment: payment ? { ...payment } : null,
|
|
3496
|
+
collectionDate,
|
|
3497
|
+
servicePeriodFrom: lines.map((line) => line.periodFrom).sort()[0],
|
|
3498
|
+
servicePeriodUntil: lines.map((line) => line.periodUntil).sort().at(-1),
|
|
3499
|
+
currency: currencyOf(group),
|
|
3500
|
+
issuer: input.issuer,
|
|
3501
|
+
subscriber: input.subscriber,
|
|
3502
|
+
footer: [...input.footer],
|
|
3503
|
+
taxTreatment: treatment,
|
|
3504
|
+
taxes: tax.rates,
|
|
3505
|
+
totalNet: tax.net,
|
|
3506
|
+
totalTax: tax.tax,
|
|
3507
|
+
totalGross: tax.gross,
|
|
3508
|
+
lines
|
|
3509
|
+
};
|
|
3510
|
+
}
|
|
3511
|
+
function invoiceContentDraftOf(invoice) {
|
|
3512
|
+
return {
|
|
3513
|
+
issuer: invoice.issuer,
|
|
3514
|
+
subscriber: invoice.subscriber,
|
|
3515
|
+
treatment: invoice.taxTreatment,
|
|
3516
|
+
issueDate: invoice.issueDate,
|
|
3517
|
+
dueDate: invoice.dueDate,
|
|
3518
|
+
servicePeriod: { from: invoice.servicePeriodFrom, until: invoice.servicePeriodUntil },
|
|
3519
|
+
lines: invoice.lines.map((line) => ({
|
|
3520
|
+
title: line.title,
|
|
3521
|
+
net: line.amountNet,
|
|
3522
|
+
rate: line.taxRate
|
|
3523
|
+
}))
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
function linesOf(input) {
|
|
3527
|
+
const placeOf = new Map(input.contractLines.map((line, index) => [line.id, index]));
|
|
3528
|
+
const place = (charge) => {
|
|
3529
|
+
const index = placeOf.get(charge.contractLineItemId);
|
|
3530
|
+
if (index === void 0) {
|
|
3531
|
+
throw new Error(
|
|
3532
|
+
`Charge ${charge.id} names contract line ${charge.contractLineItemId}, which contract ${input.group.contractId} does not have.`
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
return index;
|
|
3536
|
+
};
|
|
3537
|
+
return [...input.group.charges].sort(
|
|
3538
|
+
(a, b) => place(a) - place(b) || a.periodStart.getTime() - b.periodStart.getTime() || a.origin.localeCompare(b.origin)
|
|
3539
|
+
).map((charge, index) => ({
|
|
3540
|
+
position: index + 1,
|
|
3541
|
+
chargeId: charge.id,
|
|
3542
|
+
contractLineItemId: charge.contractLineItemId,
|
|
3543
|
+
title: input.contractLines[place(charge)].titleSnapshot,
|
|
3544
|
+
origin: charge.origin,
|
|
3545
|
+
source: charge.source,
|
|
3546
|
+
periodFrom: dayInZone(charge.periodStart, input.timeZone),
|
|
3547
|
+
periodUntil: lastDayInZone(
|
|
3548
|
+
{ from: charge.periodStart, until: charge.periodEnd },
|
|
3549
|
+
input.timeZone
|
|
3550
|
+
),
|
|
3551
|
+
amountNet: charge.amountNet,
|
|
3552
|
+
taxRate: input.treatment.rate
|
|
3553
|
+
}));
|
|
3554
|
+
}
|
|
3555
|
+
function currencyOf(group) {
|
|
3556
|
+
const currencies = new Set(group.charges.map((charge) => charge.currency));
|
|
3557
|
+
if (currencies.size !== 1) {
|
|
3558
|
+
throw new Error(
|
|
3559
|
+
`The charges of contract ${group.contractId} booked at ${group.bookedAt.toISOString()} are in ${[...currencies].join(" and ")}; an invoice is in one currency.`
|
|
3560
|
+
);
|
|
3561
|
+
}
|
|
3562
|
+
return [...currencies][0];
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
// src/invoice-issuer.ts
|
|
3566
|
+
function invoiceIssuerOf(copy, configured, history) {
|
|
3567
|
+
const target = configured && identityOf2(configured);
|
|
3568
|
+
let reached = identityOf2(copy);
|
|
3569
|
+
if (!target || !reached) return { ...copy };
|
|
3570
|
+
for (const step of history) {
|
|
3571
|
+
if (sameLegalIdentity(reached, target)) break;
|
|
3572
|
+
const change = classifyIssuerChange(
|
|
3573
|
+
recordedIssuerIdentity(step.previous),
|
|
3574
|
+
issuerBlockOf(step.current)
|
|
3575
|
+
);
|
|
3576
|
+
if (change.kind === "corrected" && sameLegalIdentity(change.recorded, reached)) {
|
|
3577
|
+
reached = change.current;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
return sameLegalIdentity(reached, target) ? { ...configured } : { ...copy };
|
|
3581
|
+
}
|
|
3582
|
+
function invoiceFooterOf(issuer, configured, footer) {
|
|
3583
|
+
const named = identityOf2(issuer);
|
|
3584
|
+
const target = configured && identityOf2(configured);
|
|
3585
|
+
return named && target && sameLegalIdentity(named, target) ? [...footer] : [];
|
|
3586
|
+
}
|
|
3587
|
+
function identityOf2(party) {
|
|
3588
|
+
return recordedIssuerIdentity({ issuer: party });
|
|
3589
|
+
}
|
|
3590
|
+
function issuerBlockOf(values) {
|
|
3591
|
+
const issuer = values.issuer;
|
|
3592
|
+
if (issuer === null || typeof issuer !== "object" || Array.isArray(issuer)) return void 0;
|
|
3593
|
+
return issuer;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
// src/invoice-document.types.ts
|
|
3597
|
+
var INVOICE_DOCUMENT_CAPABILITY = "invoices.read";
|
|
3598
|
+
|
|
3599
|
+
// src/invoice-document-mapping.ts
|
|
3600
|
+
var DOCUMENTS = "subscription_invoice_documents";
|
|
3601
|
+
var ARCHIVE = "subscription_invoice_archive";
|
|
3602
|
+
var CHECKSUM = /^[0-9a-f]{64}$/;
|
|
3603
|
+
function isInvoiceDocumentChecksum(value) {
|
|
3604
|
+
return typeof value === "string" && CHECKSUM.test(value);
|
|
3605
|
+
}
|
|
3606
|
+
function toSubscriptionInvoiceDocumentRecord(row) {
|
|
3607
|
+
if (!isInvoiceDocumentChecksum(row.checksum)) {
|
|
3608
|
+
throw new Error(`${DOCUMENTS}.checksum of ${row.invoiceId} is not a SHA-256 digest.`);
|
|
3609
|
+
}
|
|
3610
|
+
const size = Number(row.size);
|
|
3611
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
3612
|
+
throw new Error(`${DOCUMENTS}.size of ${row.invoiceId} is not a size in bytes.`);
|
|
3613
|
+
}
|
|
3614
|
+
return {
|
|
3615
|
+
invoiceId: row.invoiceId,
|
|
3616
|
+
checksum: row.checksum,
|
|
3617
|
+
mediaType: row.mediaType,
|
|
3618
|
+
fileName: row.fileName,
|
|
3619
|
+
size,
|
|
3620
|
+
renderedAt: row.renderedAt,
|
|
3621
|
+
archivedAt: row.archivedAt,
|
|
3622
|
+
deliveryClaimedAt: row.deliveryClaimedAt,
|
|
3623
|
+
deliveredAt: row.deliveredAt,
|
|
3624
|
+
deliveredTo: row.deliveredTo
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
function toArchivedInvoiceDocument(row) {
|
|
3628
|
+
if (!isInvoiceDocumentChecksum(row.checksum)) {
|
|
3629
|
+
throw new Error(`${ARCHIVE}.checksum of ${row.invoiceId} is not a SHA-256 digest.`);
|
|
3630
|
+
}
|
|
3631
|
+
if (!(row.content instanceof Uint8Array)) {
|
|
3632
|
+
throw new Error(`${ARCHIVE}.content of ${row.invoiceId} is not a file.`);
|
|
3633
|
+
}
|
|
3634
|
+
return {
|
|
3635
|
+
invoiceId: row.invoiceId,
|
|
3636
|
+
checksum: row.checksum,
|
|
3637
|
+
mediaType: row.mediaType,
|
|
3638
|
+
fileName: row.fileName,
|
|
3639
|
+
// A copy that owns its bytes: a driver's `Buffer` may be a view into a
|
|
3640
|
+
// pooled allocation, and a caller handing it on must not see that pool.
|
|
3641
|
+
content: new Uint8Array(row.content),
|
|
3642
|
+
archivedAt: row.archivedAt
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
var INVOICE_ARCHIVE_TRIGGERS = [
|
|
3646
|
+
"subscription_invoice_archive_is_kept",
|
|
3647
|
+
"subscription_invoice_archive_is_not_emptied"
|
|
3648
|
+
];
|
|
3649
|
+
function invoiceArchiveProtectionProblem(enabledTriggers) {
|
|
3650
|
+
const missing = INVOICE_ARCHIVE_TRIGGERS.filter((name) => !enabledTriggers.includes(name));
|
|
3651
|
+
if (missing.length === 0) return null;
|
|
3652
|
+
return `${ARCHIVE} lacks ${missing.join(" and ")}, which keep an archived invoice document as it was issued: run @saasicat/spec/sql/1.0-an-invoice-document-is-archived.postgres.sql after db push, and enable the triggers where they were disabled.`;
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
// src/invoice-collection.types.ts
|
|
3656
|
+
function sentCollectionOf(attempt) {
|
|
3657
|
+
const { submittedAt, heldAt } = attempt;
|
|
3658
|
+
if (submittedAt === null || heldAt === null) {
|
|
3659
|
+
throw new Error(`Collection attempt ${attempt.attemptKey} has not been sent.`);
|
|
3660
|
+
}
|
|
3661
|
+
return { ...attempt, submittedAt, heldAt };
|
|
3662
|
+
}
|
|
3663
|
+
var INVOICE_COLLECTION_LEASE_MS = 15 * 6e4;
|
|
3664
|
+
var INVOICE_COLLECTION_ASK_EVERY_MS = 60 * 6e4;
|
|
3665
|
+
function collectionAttemptKey(invoiceId, attempt) {
|
|
3666
|
+
if (!Number.isInteger(attempt) || attempt < 1) {
|
|
3667
|
+
throw new RangeError(`A collection attempt counts from 1; ${attempt} is not one.`);
|
|
3668
|
+
}
|
|
3669
|
+
return `saasicat:collect:${invoiceId}:${attempt}`;
|
|
3670
|
+
}
|
|
3671
|
+
function collectionStatusOf(outcome) {
|
|
3672
|
+
if (outcome.status === "succeeded") return "SUCCEEDED";
|
|
3673
|
+
if (outcome.status === "pending") return "PENDING";
|
|
3674
|
+
return "FAILED";
|
|
3675
|
+
}
|
|
3676
|
+
var COLLECTION_NOT_SENT_PAID = {
|
|
3677
|
+
status: "failed",
|
|
3678
|
+
paymentRef: null,
|
|
3679
|
+
reason: "other",
|
|
3680
|
+
gatewayCode: "saasicat_invoice_paid"
|
|
3681
|
+
};
|
|
3682
|
+
var COLLECTION_NOT_SENT_REVERSED = {
|
|
3683
|
+
status: "failed",
|
|
3684
|
+
paymentRef: null,
|
|
3685
|
+
reason: "other",
|
|
3686
|
+
gatewayCode: "saasicat_payment_method_reversed"
|
|
3687
|
+
};
|
|
3688
|
+
|
|
3689
|
+
// src/invoice-collection-mapping.ts
|
|
3690
|
+
var TABLE5 = "subscription_invoice_collections";
|
|
3691
|
+
var INVOICE_COLLECTION_STATUSES = [
|
|
3692
|
+
"SCHEDULED",
|
|
3693
|
+
"SUBMITTED",
|
|
3694
|
+
"PENDING",
|
|
3695
|
+
"SUCCEEDED",
|
|
3696
|
+
"FAILED",
|
|
3697
|
+
"WITHDRAWN",
|
|
3698
|
+
"REVERSED"
|
|
3699
|
+
];
|
|
3700
|
+
var UNSETTLED_COLLECTION_STATUSES = [
|
|
3701
|
+
"SUBMITTED",
|
|
3702
|
+
"PENDING"
|
|
3703
|
+
];
|
|
3704
|
+
var BLOCKING_COLLECTION_STATUSES = [
|
|
3705
|
+
"SCHEDULED",
|
|
3706
|
+
...UNSETTLED_COLLECTION_STATUSES,
|
|
3707
|
+
"SUCCEEDED"
|
|
3708
|
+
];
|
|
3709
|
+
var FOLLOWED_UP_COLLECTION_STATUSES = [
|
|
3710
|
+
"FAILED",
|
|
3711
|
+
"REVERSED"
|
|
3712
|
+
];
|
|
3713
|
+
var REVERSIBLE_COLLECTION_STATUSES = [
|
|
3714
|
+
"SUCCEEDED",
|
|
3715
|
+
"FAILED"
|
|
3716
|
+
];
|
|
3717
|
+
function lastCollectionOf(earlier) {
|
|
3718
|
+
return earlier.reduce((last, row) => Math.max(last, row.attempt), 0);
|
|
3719
|
+
}
|
|
3720
|
+
function refusesFirstAttempt(earlier) {
|
|
3721
|
+
return earlier.length > 0;
|
|
3722
|
+
}
|
|
3723
|
+
function refusesPlannedAttempt(earlier, after) {
|
|
3724
|
+
return earlier.some((row) => BLOCKING.has(row.status)) || lastCollectionOf(earlier) !== after;
|
|
3725
|
+
}
|
|
3726
|
+
var BLOCKING = new Set(BLOCKING_COLLECTION_STATUSES);
|
|
3727
|
+
var NOT_SENT = /* @__PURE__ */ new Set(["SCHEDULED", "WITHDRAWN"]);
|
|
3728
|
+
var COLLECTION_FAILURE_REASONS = [
|
|
3729
|
+
"insufficient-funds",
|
|
3730
|
+
"declined",
|
|
3731
|
+
"authentication-required",
|
|
3732
|
+
"payment-method-unusable",
|
|
3733
|
+
"mandate-invalid",
|
|
3734
|
+
"other"
|
|
3735
|
+
];
|
|
3736
|
+
function toInvoiceCollectionRecord(row) {
|
|
3737
|
+
const where = (column) => ({ table: TABLE5, column, id: row.id });
|
|
3738
|
+
const amount = Number(
|
|
3739
|
+
// oxlint-disable-next-line typescript/no-base-to-string -- Prisma hands a Decimal, whose text is the number; any other object becomes NaN and is refused below
|
|
3740
|
+
row.amount === null || row.amount === void 0 ? NaN : String(row.amount)
|
|
3741
|
+
);
|
|
3742
|
+
if (!Number.isFinite(amount)) {
|
|
3743
|
+
throw new Error(`${TABLE5}.amount of ${row.id} is not an amount.`);
|
|
3744
|
+
}
|
|
3745
|
+
const status = oneOf(INVOICE_COLLECTION_STATUSES, row.status, where("status"));
|
|
3746
|
+
const sent = !NOT_SENT.has(status);
|
|
3747
|
+
if (sent !== (row.submittedAt !== null) || sent !== (row.heldAt !== null)) {
|
|
3748
|
+
throw new Error(
|
|
3749
|
+
`${TABLE5}.submittedAt and heldAt of ${row.id} do not fit its status ${status}: an attempt that left carries both, and one that did not carries neither.`
|
|
3750
|
+
);
|
|
3751
|
+
}
|
|
3752
|
+
const collectOn = row.collectOn === null || row.collectOn === void 0 ? null : dayOf2(row.collectOn, where("collectOn"));
|
|
3753
|
+
if (status === "SCHEDULED" && collectOn === null) {
|
|
3754
|
+
throw new Error(`${TABLE5}.collectOn of ${row.id} is missing from a planned attempt.`);
|
|
3755
|
+
}
|
|
3756
|
+
return {
|
|
3757
|
+
id: row.id,
|
|
3758
|
+
invoiceId: row.invoiceId,
|
|
3759
|
+
attempt: row.attempt,
|
|
3760
|
+
attemptKey: row.attemptKey,
|
|
3761
|
+
gatewayAccount: row.gatewayAccount,
|
|
3762
|
+
paymentMethodId: row.paymentMethodId,
|
|
3763
|
+
paymentMethodType: oneOf(
|
|
3764
|
+
PAYMENT_METHOD_TYPES,
|
|
3765
|
+
row.paymentMethodType,
|
|
3766
|
+
where("paymentMethodType")
|
|
3767
|
+
),
|
|
3768
|
+
customerRef: row.customerRef,
|
|
3769
|
+
paymentMethodRef: row.paymentMethodRef,
|
|
3770
|
+
invoiceNumber: row.invoiceNumber,
|
|
3771
|
+
amount,
|
|
3772
|
+
currency: row.currency,
|
|
3773
|
+
status,
|
|
3774
|
+
paymentRef: row.paymentRef,
|
|
3775
|
+
failureReason: row.failureReason === null ? null : oneOf(COLLECTION_FAILURE_REASONS, row.failureReason, where("failureReason")),
|
|
3776
|
+
failureCode: row.failureCode,
|
|
3777
|
+
submittedAt: row.submittedAt,
|
|
3778
|
+
heldAt: row.heldAt,
|
|
3779
|
+
resolvedAt: row.resolvedAt,
|
|
3780
|
+
collectOn,
|
|
3781
|
+
announcedAt: row.announcedAt,
|
|
3782
|
+
followedUpAt: row.followedUpAt
|
|
3783
|
+
};
|
|
3784
|
+
}
|
|
3785
|
+
function invoiceCollectionColumns(attempt, number) {
|
|
3786
|
+
return {
|
|
3787
|
+
invoiceId: attempt.invoiceId,
|
|
3788
|
+
attempt: number,
|
|
3789
|
+
attemptKey: collectionAttemptKey(attempt.invoiceId, number),
|
|
3790
|
+
gatewayAccount: attempt.gatewayAccount,
|
|
3791
|
+
paymentMethodId: attempt.paymentMethodId,
|
|
3792
|
+
paymentMethodType: attempt.paymentMethodType,
|
|
3793
|
+
customerRef: attempt.customerRef,
|
|
3794
|
+
paymentMethodRef: attempt.paymentMethodRef,
|
|
3795
|
+
invoiceNumber: attempt.invoiceNumber,
|
|
3796
|
+
amount: attempt.amount.toFixed(2),
|
|
3797
|
+
currency: attempt.currency,
|
|
3798
|
+
status: "SUBMITTED",
|
|
3799
|
+
submittedAt: attempt.at,
|
|
3800
|
+
heldAt: attempt.at,
|
|
3801
|
+
collectOn: null
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
function plannedCollectionColumns(plan, number) {
|
|
3805
|
+
return {
|
|
3806
|
+
invoiceId: plan.invoiceId,
|
|
3807
|
+
attempt: number,
|
|
3808
|
+
attemptKey: collectionAttemptKey(plan.invoiceId, number),
|
|
3809
|
+
gatewayAccount: plan.gatewayAccount,
|
|
3810
|
+
paymentMethodId: plan.paymentMethodId,
|
|
3811
|
+
paymentMethodType: plan.paymentMethodType,
|
|
3812
|
+
customerRef: plan.customerRef,
|
|
3813
|
+
paymentMethodRef: plan.paymentMethodRef,
|
|
3814
|
+
invoiceNumber: plan.invoiceNumber,
|
|
3815
|
+
amount: plan.amount.toFixed(2),
|
|
3816
|
+
currency: plan.currency,
|
|
3817
|
+
status: "SCHEDULED",
|
|
3818
|
+
submittedAt: null,
|
|
3819
|
+
heldAt: null,
|
|
3820
|
+
collectOn: plan.collectOn
|
|
3821
|
+
};
|
|
3822
|
+
}
|
|
3823
|
+
function collectionOutcomeColumns(outcome, at) {
|
|
3824
|
+
const failed = outcome.status === "failed" ? outcome : null;
|
|
3825
|
+
return {
|
|
3826
|
+
status: collectionStatusOf(outcome),
|
|
3827
|
+
paymentRef: outcome.paymentRef,
|
|
3828
|
+
failureReason: failed?.reason ?? null,
|
|
3829
|
+
failureCode: failed?.gatewayCode ?? null,
|
|
3830
|
+
resolvedAt: outcome.status === "pending" ? null : at
|
|
3831
|
+
};
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
// src/invoice-collection-plan.ts
|
|
3835
|
+
var COLLECTION_RETRY_AFTER_DAYS = 7;
|
|
3836
|
+
function isChargedAgain(failed, attempts) {
|
|
3837
|
+
if (failed.status !== "FAILED" || failed.failureReason === null) return false;
|
|
3838
|
+
if (!isRetryableCollectionFailure(failed.failureReason)) return false;
|
|
3839
|
+
return !attempts.some(
|
|
3840
|
+
(other) => other.id !== failed.id && other.status === "FAILED" && other.paymentMethodId === failed.paymentMethodId
|
|
3841
|
+
);
|
|
3842
|
+
}
|
|
3843
|
+
function chargedAgainOn(failedOn) {
|
|
3844
|
+
return addDaysToDay(failedOn, COLLECTION_RETRY_AFTER_DAYS);
|
|
3845
|
+
}
|
|
3846
|
+
function announcedCollectionDay(earliest2, announcedOn, leadDays) {
|
|
3847
|
+
const afterLead = addDaysToDay(announcedOn, leadDays);
|
|
3848
|
+
return afterLead > earliest2 ? afterLead : earliest2;
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3851
|
+
// src/invoice-collection-notice-mapping.ts
|
|
3852
|
+
var TABLE6 = "subscription_notices";
|
|
3853
|
+
var NEXT_ACTIONS = [
|
|
3854
|
+
"charged-again",
|
|
3855
|
+
"announced-again",
|
|
3856
|
+
"replace-payment-method"
|
|
3857
|
+
];
|
|
3858
|
+
var AFTER_REVERSAL_ACTIONS = [
|
|
3859
|
+
"collected-with-payment-method-in-use",
|
|
3860
|
+
"replace-payment-method"
|
|
3861
|
+
];
|
|
3862
|
+
var NoticeFields = class _NoticeFields {
|
|
3863
|
+
constructor(content, id) {
|
|
3864
|
+
this.content = content;
|
|
3865
|
+
this.id = id;
|
|
3866
|
+
}
|
|
3867
|
+
content;
|
|
3868
|
+
id;
|
|
3869
|
+
where(column) {
|
|
3870
|
+
return { table: TABLE6, column: `content.${column}`, id: this.id };
|
|
3871
|
+
}
|
|
3872
|
+
value(name) {
|
|
3873
|
+
if (this.content === null || typeof this.content !== "object") {
|
|
3874
|
+
throw new Error(`${TABLE6}.content of ${this.id} is not a notice.`);
|
|
3875
|
+
}
|
|
3876
|
+
return Object.getOwnPropertyDescriptor(this.content, name)?.value;
|
|
3877
|
+
}
|
|
3878
|
+
text(name) {
|
|
3879
|
+
const value = this.value(name);
|
|
3880
|
+
if (typeof value !== "string" || value === "") {
|
|
3881
|
+
throw new Error(`${TABLE6}.content.${name} of ${this.id} is not a text.`);
|
|
3882
|
+
}
|
|
3883
|
+
return value;
|
|
3884
|
+
}
|
|
3885
|
+
amount(name) {
|
|
3886
|
+
const value = this.value(name);
|
|
3887
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
3888
|
+
throw new Error(`${TABLE6}.content.${name} of ${this.id} is not an amount.`);
|
|
3889
|
+
}
|
|
3890
|
+
return value;
|
|
3891
|
+
}
|
|
3892
|
+
day(name) {
|
|
3893
|
+
return dayOf2(this.value(name), this.where(name));
|
|
3894
|
+
}
|
|
3895
|
+
oneOf(allowed, name) {
|
|
3896
|
+
return oneOf(allowed, this.text(name), this.where(name));
|
|
3897
|
+
}
|
|
3898
|
+
nested(name) {
|
|
3899
|
+
return new _NoticeFields(this.value(name), this.id);
|
|
3900
|
+
}
|
|
3901
|
+
invoice() {
|
|
3902
|
+
return {
|
|
3903
|
+
tenantId: this.text("tenantId"),
|
|
3904
|
+
subscriptionId: this.text("subscriptionId"),
|
|
3905
|
+
subscriberId: this.text("subscriberId"),
|
|
3906
|
+
invoiceId: this.text("invoiceId"),
|
|
3907
|
+
invoiceNumber: this.text("invoiceNumber"),
|
|
3908
|
+
amount: this.amount("amount"),
|
|
3909
|
+
currency: this.text("currency")
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
function toCollectionFailedNotice(content, id) {
|
|
3914
|
+
const fields = new NoticeFields(content, id);
|
|
3915
|
+
const next = fields.nested("next");
|
|
3916
|
+
const action = next.oneOf(NEXT_ACTIONS, "action");
|
|
3917
|
+
return {
|
|
3918
|
+
kind: fields.oneOf(["collection-failed"], "kind"),
|
|
3919
|
+
...fields.invoice(),
|
|
3920
|
+
paymentMethodType: fields.oneOf(PAYMENT_METHOD_TYPES, "paymentMethodType"),
|
|
3921
|
+
reason: fields.oneOf(COLLECTION_FAILURE_REASONS, "reason"),
|
|
3922
|
+
next: action === "charged-again" ? { action, on: next.day("on") } : { action }
|
|
3923
|
+
};
|
|
3924
|
+
}
|
|
3925
|
+
function toCollectionAnnouncedNotice(content, id) {
|
|
3926
|
+
const fields = new NoticeFields(content, id);
|
|
3927
|
+
return {
|
|
3928
|
+
kind: fields.oneOf(["collection-announced"], "kind"),
|
|
3929
|
+
...fields.invoice(),
|
|
3930
|
+
collectOn: fields.day("collectOn"),
|
|
3931
|
+
mandateReference: fields.text("mandateReference"),
|
|
3932
|
+
last4: fields.text("last4")
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
function toCollectionReversedNotice(content, id) {
|
|
3936
|
+
const fields = new NoticeFields(content, id);
|
|
3937
|
+
return {
|
|
3938
|
+
kind: fields.oneOf(["collection-reversed"], "kind"),
|
|
3939
|
+
...fields.invoice(),
|
|
3940
|
+
paymentMethodType: fields.oneOf(PAYMENT_METHOD_TYPES, "paymentMethodType"),
|
|
3941
|
+
owed: fields.amount("owed"),
|
|
3942
|
+
next: { action: fields.nested("next").oneOf(AFTER_REVERSAL_ACTIONS, "action") }
|
|
3943
|
+
};
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
// src/subscriber-payment.types.ts
|
|
3947
|
+
var SUBSCRIBER_CREDIT_CAPABILITY = "credits.read";
|
|
3948
|
+
var SUBSCRIBER_PAYOUT_CAPABILITY = "credits.payout";
|
|
3949
|
+
|
|
3950
|
+
// src/column-amount.ts
|
|
3951
|
+
function columnAmountOf(value, where) {
|
|
3952
|
+
const amount = Number(value === null || value === void 0 ? NaN : String(value));
|
|
3953
|
+
if (!Number.isFinite(amount)) {
|
|
3954
|
+
throw new Error(`${where.table}.${where.column} of ${where.id} is not an amount.`);
|
|
3955
|
+
}
|
|
3956
|
+
return amount;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
// src/subscriber-payment-mapping.ts
|
|
3960
|
+
var PAYMENTS = "subscriber_payments";
|
|
3961
|
+
var CREDITS = "subscriber_credits";
|
|
3962
|
+
var CREDIT_ENTRIES = "subscriber_credit_entries";
|
|
3963
|
+
var REFUNDS = "subscriber_refunds";
|
|
3964
|
+
var SETTLEMENTS = "subscription_invoice_settlements";
|
|
3965
|
+
var REVERSALS = "subscriber_reversals";
|
|
3966
|
+
var INVOICE_SETTLEMENT_KINDS = [
|
|
3967
|
+
"PAYMENT",
|
|
3968
|
+
"REVERSAL",
|
|
3969
|
+
"REINSTATEMENT"
|
|
3970
|
+
];
|
|
3971
|
+
var REOPENING_SETTLEMENT_KINDS = ["REVERSAL"];
|
|
3972
|
+
var SUBSCRIBER_CREDIT_ENTRY_KINDS = [
|
|
3973
|
+
"REFUND",
|
|
3974
|
+
"REFUND_FAILED",
|
|
3975
|
+
"PAYOUT",
|
|
3976
|
+
"REVERSAL",
|
|
3977
|
+
"REVERSAL_TAKEN_BACK"
|
|
3978
|
+
];
|
|
3979
|
+
var REFUND_ENTRY_KINDS = /* @__PURE__ */ new Set([
|
|
3980
|
+
"REFUND",
|
|
3981
|
+
"REFUND_FAILED"
|
|
3982
|
+
]);
|
|
3983
|
+
var REVERSAL_ENTRY_KINDS = /* @__PURE__ */ new Set([
|
|
3984
|
+
"REVERSAL",
|
|
3985
|
+
"REVERSAL_TAKEN_BACK"
|
|
3986
|
+
]);
|
|
3987
|
+
var SUBSCRIBER_REFUND_STATUSES = [
|
|
3988
|
+
"PENDING",
|
|
3989
|
+
"SUCCEEDED",
|
|
3990
|
+
"FAILED",
|
|
3991
|
+
"CANCELED"
|
|
3992
|
+
];
|
|
3993
|
+
var ENDED_REFUND_STATUSES = ["FAILED", "CANCELED"];
|
|
3994
|
+
function isEndedRefund(status) {
|
|
3995
|
+
return ENDED_REFUND_STATUSES.includes(status);
|
|
3996
|
+
}
|
|
3997
|
+
function toSubscriberPaymentRecord(row) {
|
|
3998
|
+
return {
|
|
3999
|
+
id: row.id,
|
|
4000
|
+
subscriberId: row.subscriberId,
|
|
4001
|
+
tenantId: row.tenantId,
|
|
4002
|
+
invoiceId: row.invoiceId,
|
|
4003
|
+
collectionId: row.collectionId,
|
|
4004
|
+
gatewayAccount: row.gatewayAccount,
|
|
4005
|
+
provider: row.provider,
|
|
4006
|
+
gatewayRef: row.gatewayRef,
|
|
4007
|
+
amount: columnAmountOf(row.amount, { table: PAYMENTS, column: "amount", id: row.id }),
|
|
4008
|
+
currency: row.currency,
|
|
4009
|
+
recordedAt: row.recordedAt
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
function subscriberPaymentColumns(payment, invoice) {
|
|
4013
|
+
return {
|
|
4014
|
+
subscriberId: invoice.subscriberId,
|
|
4015
|
+
tenantId: invoice.tenantId,
|
|
4016
|
+
invoiceId: payment.invoiceId,
|
|
4017
|
+
collectionId: payment.collectionId,
|
|
4018
|
+
gatewayAccount: payment.gatewayAccount,
|
|
4019
|
+
provider: payment.provider,
|
|
4020
|
+
gatewayRef: payment.gatewayRef,
|
|
4021
|
+
amount: toDecimalString(payment.amount),
|
|
4022
|
+
currency: payment.currency,
|
|
4023
|
+
recordedAt: payment.at
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
function settledAmountOf(row) {
|
|
4027
|
+
const kind = oneOf(INVOICE_SETTLEMENT_KINDS, row.kind, {
|
|
4028
|
+
table: SETTLEMENTS,
|
|
4029
|
+
column: "kind",
|
|
4030
|
+
id: row.id
|
|
4031
|
+
});
|
|
4032
|
+
const amount = columnAmountOf(row.amount, { table: SETTLEMENTS, column: "amount", id: row.id });
|
|
4033
|
+
return REOPENING_SETTLEMENT_KINDS.includes(kind) ? -amount : amount;
|
|
4034
|
+
}
|
|
4035
|
+
function toSubscriberCreditRecord(row) {
|
|
4036
|
+
return {
|
|
4037
|
+
id: row.id,
|
|
4038
|
+
subscriberId: row.subscriberId,
|
|
4039
|
+
tenantId: row.tenantId,
|
|
4040
|
+
amount: columnAmountOf(row.amount, { table: CREDITS, column: "amount", id: row.id }),
|
|
4041
|
+
currency: row.currency,
|
|
4042
|
+
origin: creditOriginOf(row),
|
|
4043
|
+
createdAt: row.createdAt
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
function creditOriginOf(row) {
|
|
4047
|
+
const origins = [];
|
|
4048
|
+
if (row.originPaymentId !== null) {
|
|
4049
|
+
origins.push({ kind: "payment", paymentId: row.originPaymentId });
|
|
4050
|
+
}
|
|
4051
|
+
if (row.originInvoiceId !== null) {
|
|
4052
|
+
origins.push({ kind: "invoice", invoiceId: row.originInvoiceId });
|
|
4053
|
+
}
|
|
4054
|
+
if (row.originReversalId !== null) {
|
|
4055
|
+
origins.push({ kind: "reversal", reversalId: row.originReversalId });
|
|
4056
|
+
}
|
|
4057
|
+
const [origin] = origins;
|
|
4058
|
+
if (origin && origins.length === 1) return origin;
|
|
4059
|
+
throw new Error(
|
|
4060
|
+
`${CREDITS} row '${row.id}' names ${origins.length === 0 ? "none" : String(origins.length)} of a payment, an invoice and a reversal it comes from; a credit comes from exactly one.`
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
function toSubscriberCreditEntryRecord(row) {
|
|
4064
|
+
const where = (column) => ({ table: CREDIT_ENTRIES, column, id: row.id });
|
|
4065
|
+
const kind = oneOf(SUBSCRIBER_CREDIT_ENTRY_KINDS, row.kind, where("kind"));
|
|
4066
|
+
const paidOn = row.paidOn === null || row.paidOn === void 0 ? null : dayOf2(row.paidOn, where("paidOn"));
|
|
4067
|
+
if (REFUND_ENTRY_KINDS.has(kind) !== (row.refundId !== null)) {
|
|
4068
|
+
throw new Error(
|
|
4069
|
+
`${CREDIT_ENTRIES} row '${row.id}' is a ${kind} ${row.refundId === null ? "without" : "with"} a refund; the entries of a refund name it, and no other entry does.`
|
|
4070
|
+
);
|
|
4071
|
+
}
|
|
4072
|
+
if (REVERSAL_ENTRY_KINDS.has(kind) !== (row.reversalId !== null)) {
|
|
4073
|
+
throw new Error(
|
|
4074
|
+
`${CREDIT_ENTRIES} row '${row.id}' is a ${kind} ${row.reversalId === null ? "without" : "with"} a reversal; the entries of a reversal name it, and no other entry does.`
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
4077
|
+
return {
|
|
4078
|
+
id: row.id,
|
|
4079
|
+
creditId: row.creditId,
|
|
4080
|
+
kind,
|
|
4081
|
+
amount: columnAmountOf(row.amount, where("amount")),
|
|
4082
|
+
refundId: row.refundId,
|
|
4083
|
+
reversalId: row.reversalId,
|
|
4084
|
+
reference: row.reference,
|
|
4085
|
+
paidOn,
|
|
4086
|
+
recordedBy: row.recordedBy,
|
|
4087
|
+
note: row.note,
|
|
4088
|
+
recordedAt: row.recordedAt
|
|
4089
|
+
};
|
|
4090
|
+
}
|
|
4091
|
+
function refundEntryColumns(creditId, refund, kind, amount, at) {
|
|
4092
|
+
return {
|
|
4093
|
+
creditId,
|
|
4094
|
+
kind,
|
|
4095
|
+
amount: toDecimalString(amount),
|
|
4096
|
+
refundId: refund.id,
|
|
4097
|
+
reversalId: null,
|
|
4098
|
+
reference: refund.gatewayRef,
|
|
4099
|
+
paidOn: null,
|
|
4100
|
+
recordedBy: null,
|
|
4101
|
+
note: null,
|
|
4102
|
+
recordedAt: at
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
function payoutEntryColumns(payout) {
|
|
4106
|
+
return {
|
|
4107
|
+
creditId: payout.creditId,
|
|
4108
|
+
kind: "PAYOUT",
|
|
4109
|
+
amount: toDecimalString(payout.amount),
|
|
4110
|
+
refundId: null,
|
|
4111
|
+
reversalId: null,
|
|
4112
|
+
reference: payout.reference,
|
|
4113
|
+
paidOn: payout.paidOn,
|
|
4114
|
+
recordedBy: payout.recordedBy,
|
|
4115
|
+
note: payout.note,
|
|
4116
|
+
recordedAt: payout.at
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
function toSubscriberRefundRecord(row) {
|
|
4120
|
+
const where = (column) => ({ table: REFUNDS, column, id: row.id });
|
|
4121
|
+
const status = oneOf(SUBSCRIBER_REFUND_STATUSES, row.status, where("status"));
|
|
4122
|
+
if (isEndedRefund(status) !== (row.endedAt !== null)) {
|
|
4123
|
+
throw new Error(
|
|
4124
|
+
`${REFUNDS}.endedAt of ${row.id} does not fit its status ${status}: a refund that failed or was canceled carries when, and one that pays out does not.`
|
|
4125
|
+
);
|
|
4126
|
+
}
|
|
4127
|
+
return {
|
|
4128
|
+
id: row.id,
|
|
4129
|
+
subscriberId: row.subscriberId,
|
|
4130
|
+
paymentId: row.paymentId,
|
|
4131
|
+
gatewayAccount: row.gatewayAccount,
|
|
4132
|
+
gatewayRef: row.gatewayRef,
|
|
4133
|
+
amount: columnAmountOf(row.amount, where("amount")),
|
|
4134
|
+
currency: row.currency,
|
|
4135
|
+
status,
|
|
4136
|
+
recordedAt: row.recordedAt,
|
|
4137
|
+
endedAt: row.endedAt
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
function subscriberRefundColumns(refund, payment) {
|
|
4141
|
+
return {
|
|
4142
|
+
subscriberId: payment.subscriberId,
|
|
4143
|
+
paymentId: payment.id,
|
|
4144
|
+
gatewayAccount: refund.gatewayAccount,
|
|
4145
|
+
gatewayRef: refund.gatewayRef,
|
|
4146
|
+
amount: toDecimalString(refund.amount),
|
|
4147
|
+
currency: refund.currency,
|
|
4148
|
+
status: refund.status,
|
|
4149
|
+
recordedAt: refund.at,
|
|
4150
|
+
endedAt: isEndedRefund(refund.status) ? refund.at : null
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
function reversalEntryColumns(creditId, reversal, kind, amount, at) {
|
|
4154
|
+
return {
|
|
4155
|
+
creditId,
|
|
4156
|
+
kind,
|
|
4157
|
+
amount: toDecimalString(amount),
|
|
4158
|
+
refundId: null,
|
|
4159
|
+
reversalId: reversal.id,
|
|
4160
|
+
reference: reversal.gatewayRef,
|
|
4161
|
+
paidOn: null,
|
|
4162
|
+
recordedBy: null,
|
|
4163
|
+
note: null,
|
|
4164
|
+
recordedAt: at
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
function toSubscriberReversalRecord(row) {
|
|
4168
|
+
const where = (column) => ({ table: REVERSALS, column, id: row.id });
|
|
4169
|
+
const amount = columnAmountOf(row.amount, where("amount"));
|
|
4170
|
+
const fromCredit = columnAmountOf(row.fromCredit, where("fromCredit"));
|
|
4171
|
+
const reinstated = columnAmountOf(row.reinstated, where("reinstated"));
|
|
4172
|
+
const within = (part) => part >= 0 && toCents(part) <= toCents(amount);
|
|
4173
|
+
if (!within(fromCredit) || !within(reinstated)) {
|
|
4174
|
+
throw new Error(
|
|
4175
|
+
`${REVERSALS} row '${row.id}' takes ${fromCredit} from the credit and is given back ${reinstated} of the ${amount} it withdrew; each is a part of it.`
|
|
4176
|
+
);
|
|
4177
|
+
}
|
|
4178
|
+
if (reinstated > 0 !== (row.reinstatedAt !== null)) {
|
|
4179
|
+
throw new Error(
|
|
4180
|
+
`${REVERSALS}.reinstatedAt of ${row.id} does not fit what was given back of it: a reversal given something back carries when, and one given nothing does not.`
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
return {
|
|
4184
|
+
id: row.id,
|
|
4185
|
+
subscriberId: row.subscriberId,
|
|
4186
|
+
tenantId: row.tenantId,
|
|
4187
|
+
paymentId: row.paymentId,
|
|
4188
|
+
gatewayAccount: row.gatewayAccount,
|
|
4189
|
+
gatewayRef: row.gatewayRef,
|
|
4190
|
+
amount,
|
|
4191
|
+
currency: row.currency,
|
|
4192
|
+
fromCredit,
|
|
4193
|
+
reinstated,
|
|
4194
|
+
recordedAt: row.recordedAt,
|
|
4195
|
+
reinstatedAt: row.reinstatedAt
|
|
4196
|
+
};
|
|
4197
|
+
}
|
|
4198
|
+
function subscriberReversalColumns(reversal, payment) {
|
|
4199
|
+
return {
|
|
4200
|
+
subscriberId: payment.subscriberId,
|
|
4201
|
+
tenantId: payment.tenantId,
|
|
4202
|
+
paymentId: payment.id,
|
|
4203
|
+
gatewayAccount: reversal.gatewayAccount,
|
|
4204
|
+
gatewayRef: reversal.gatewayRef,
|
|
4205
|
+
amount: toDecimalString(0),
|
|
4206
|
+
currency: reversal.currency,
|
|
4207
|
+
fromCredit: toDecimalString(0),
|
|
4208
|
+
reinstated: toDecimalString(0),
|
|
4209
|
+
recordedAt: reversal.at,
|
|
4210
|
+
reinstatedAt: null
|
|
4211
|
+
};
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
// src/subscriber-payment-rules.ts
|
|
4215
|
+
function owedOn(gross, settled) {
|
|
4216
|
+
const paid = settled.reduce((sum2, amount) => sum2 + toCents(amount), 0);
|
|
4217
|
+
return (toCents(gross) - paid) / 100;
|
|
4218
|
+
}
|
|
4219
|
+
function paymentSettlement(amount, owed) {
|
|
4220
|
+
const brought = toCents(amount);
|
|
4221
|
+
const settles = Math.min(brought, Math.max(0, toCents(owed)));
|
|
4222
|
+
return { settles: settles / 100, credits: (brought - settles) / 100 };
|
|
4223
|
+
}
|
|
4224
|
+
var GIVING_BACK_CREDIT_ENTRY_KINDS = [
|
|
4225
|
+
"REFUND_FAILED",
|
|
4226
|
+
"REVERSAL_TAKEN_BACK"
|
|
4227
|
+
];
|
|
4228
|
+
var GIVING_BACK = new Set(GIVING_BACK_CREDIT_ENTRY_KINDS);
|
|
4229
|
+
function paidOutBy(entry) {
|
|
4230
|
+
const cents2 = toCents(entry.amount);
|
|
4231
|
+
return GIVING_BACK.has(entry.kind) ? -cents2 : cents2;
|
|
4232
|
+
}
|
|
4233
|
+
function openOfCredit(amount, entries) {
|
|
4234
|
+
const paidOut = entries.reduce((sum2, entry) => sum2 + paidOutBy(entry), 0);
|
|
4235
|
+
return Math.max(0, toCents(amount) - paidOut) / 100;
|
|
4236
|
+
}
|
|
4237
|
+
function refundParts(amount, paymentId, credits) {
|
|
4238
|
+
const ownFirst = [...credits].sort(
|
|
4239
|
+
(a, b) => rankFor(a, paymentId) - rankFor(b, paymentId) || a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id, "en")
|
|
4240
|
+
);
|
|
4241
|
+
let left = toCents(amount);
|
|
4242
|
+
const parts = [];
|
|
4243
|
+
for (const credit of ownFirst) {
|
|
4244
|
+
if (left === 0) break;
|
|
4245
|
+
const taken = Math.min(left, toCents(credit.open));
|
|
4246
|
+
if (taken <= 0) continue;
|
|
4247
|
+
parts.push({ creditId: credit.id, amount: taken / 100 });
|
|
4248
|
+
left -= taken;
|
|
4249
|
+
}
|
|
4250
|
+
return { parts, unmatched: left / 100 };
|
|
4251
|
+
}
|
|
4252
|
+
function rankFor(credit, paymentId) {
|
|
4253
|
+
return credit.origin.kind === "payment" && credit.origin.paymentId === paymentId ? 0 : 1;
|
|
4254
|
+
}
|
|
4255
|
+
function withdrawalParts(amount, credit, invoices, covering = []) {
|
|
4256
|
+
let left = toCents(amount);
|
|
4257
|
+
const share = credit ? Math.min(left, Math.max(0, toCents(credit.left))) : 0;
|
|
4258
|
+
const open = credit ? Math.min(share, Math.max(0, toCents(credit.open))) : 0;
|
|
4259
|
+
left -= share;
|
|
4260
|
+
const opening = [];
|
|
4261
|
+
for (const invoice of invoices) {
|
|
4262
|
+
if (left === 0) break;
|
|
4263
|
+
const taken = Math.min(left, Math.max(0, toCents(invoice.left)));
|
|
4264
|
+
if (taken === 0) continue;
|
|
4265
|
+
opening.push({ invoiceId: invoice.invoiceId, cents: taken });
|
|
4266
|
+
left -= taken;
|
|
4267
|
+
}
|
|
4268
|
+
let toCover = opening.reduce((sum2, each) => sum2 + each.cents, 0);
|
|
4269
|
+
const covered = [];
|
|
4270
|
+
for (const other of covering) {
|
|
4271
|
+
if (toCover === 0) break;
|
|
4272
|
+
const taken = Math.min(toCover, Math.max(0, toCents(other.open)));
|
|
4273
|
+
if (taken === 0) continue;
|
|
4274
|
+
covered.push({ creditId: other.creditId, amount: taken / 100 });
|
|
4275
|
+
toCover -= taken;
|
|
4276
|
+
}
|
|
4277
|
+
let coveredCents = opening.reduce((sum2, each) => sum2 + each.cents, 0) - toCover;
|
|
4278
|
+
const opened = [];
|
|
4279
|
+
for (const each of opening) {
|
|
4280
|
+
const kept = Math.min(each.cents, coveredCents);
|
|
4281
|
+
coveredCents -= kept;
|
|
4282
|
+
if (each.cents > kept) {
|
|
4283
|
+
opened.push({ invoiceId: each.invoiceId, amount: (each.cents - kept) / 100 });
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
return {
|
|
4287
|
+
fromCredit: share / 100,
|
|
4288
|
+
credit: credit && open > 0 ? { creditId: credit.creditId, amount: open / 100 } : null,
|
|
4289
|
+
covering: covered,
|
|
4290
|
+
invoices: opened,
|
|
4291
|
+
unmatched: (share - open + left) / 100
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
function reinstatementParts(amount, took) {
|
|
4295
|
+
let left = toCents(amount);
|
|
4296
|
+
const covered = Math.min(left, Math.max(0, toCents(took.unmatched)));
|
|
4297
|
+
left -= covered;
|
|
4298
|
+
const credits = [];
|
|
4299
|
+
for (const credit of took.credits) {
|
|
4300
|
+
const given = Math.min(left, Math.max(0, toCents(credit.left)));
|
|
4301
|
+
if (given === 0) continue;
|
|
4302
|
+
credits.push({ creditId: credit.creditId, amount: given / 100 });
|
|
4303
|
+
left -= given;
|
|
4304
|
+
}
|
|
4305
|
+
const invoices = [];
|
|
4306
|
+
let credited = 0;
|
|
4307
|
+
for (const invoice of took.invoices) {
|
|
4308
|
+
const given = Math.min(left, Math.max(0, toCents(invoice.left)));
|
|
4309
|
+
if (given === 0) continue;
|
|
4310
|
+
const settles = Math.min(given, Math.max(0, toCents(invoice.owed)));
|
|
4311
|
+
if (settles > 0) invoices.push({ invoiceId: invoice.invoiceId, amount: settles / 100 });
|
|
4312
|
+
credited += given - settles;
|
|
4313
|
+
left -= given;
|
|
4314
|
+
}
|
|
4315
|
+
return {
|
|
4316
|
+
covered: covered / 100,
|
|
4317
|
+
credits,
|
|
4318
|
+
invoices,
|
|
4319
|
+
credited: credited / 100,
|
|
4320
|
+
beyond: left / 100
|
|
4321
|
+
};
|
|
4322
|
+
}
|
|
4323
|
+
function netByInvoice(entries) {
|
|
4324
|
+
const net = /* @__PURE__ */ new Map();
|
|
4325
|
+
for (const entry of entries) {
|
|
4326
|
+
net.set(entry.invoiceId, (net.get(entry.invoiceId) ?? 0) + toCents(entry.amount));
|
|
4327
|
+
}
|
|
4328
|
+
return [...net].map(([invoiceId, cents2]) => ({ invoiceId, amount: cents2 / 100 }));
|
|
4329
|
+
}
|
|
4330
|
+
function paidOutByCredit(entries) {
|
|
4331
|
+
const paid = /* @__PURE__ */ new Map();
|
|
4332
|
+
for (const entry of entries) {
|
|
4333
|
+
paid.set(entry.creditId, (paid.get(entry.creditId) ?? 0) + paidOutBy(entry));
|
|
4334
|
+
}
|
|
4335
|
+
return [...paid].map(([creditId, cents2]) => ({ creditId, amount: cents2 / 100 }));
|
|
4336
|
+
}
|
|
4337
|
+
function coversOf(entries, own) {
|
|
4338
|
+
const taken = /* @__PURE__ */ new Map();
|
|
4339
|
+
for (const entry of entries) {
|
|
4340
|
+
if (entry.reversalId === null || own.has(entry.reversalId)) continue;
|
|
4341
|
+
taken.set(entry.reversalId, (taken.get(entry.reversalId) ?? 0) + paidOutBy(entry));
|
|
4342
|
+
}
|
|
4343
|
+
return [...taken].filter(([, cents2]) => cents2 > 0).map(([reversalId, cents2]) => ({ reversalId, amount: cents2 / 100 }));
|
|
4344
|
+
}
|
|
4345
|
+
function coversLifted(parts, covers) {
|
|
4346
|
+
let reach = toCents(parts.fromCredit) - toCents(parts.credit?.amount ?? 0);
|
|
4347
|
+
const lifted = [];
|
|
4348
|
+
for (const cover of covers) {
|
|
4349
|
+
if (reach <= 0) break;
|
|
4350
|
+
const taken = Math.min(reach, toCents(cover.amount));
|
|
4351
|
+
if (taken <= 0) continue;
|
|
4352
|
+
lifted.push({ reversalId: cover.reversalId, amount: taken / 100 });
|
|
4353
|
+
reach -= taken;
|
|
4354
|
+
}
|
|
4355
|
+
const liftedCents = lifted.reduce((sum2, each) => sum2 + toCents(each.amount), 0);
|
|
4356
|
+
return { lifted, unmatched: (toCents(parts.unmatched) - liftedCents) / 100 };
|
|
4357
|
+
}
|
|
4358
|
+
function inWithdrawalOrder(parts, credits, paymentId) {
|
|
4359
|
+
const byId = new Map(credits.map((credit) => [credit.id, credit]));
|
|
4360
|
+
const rank = (part) => {
|
|
4361
|
+
const credit = byId.get(part.creditId);
|
|
4362
|
+
if (!credit) return [2, 0, part.creditId];
|
|
4363
|
+
const own = credit.origin.kind === "payment" && credit.origin.paymentId === paymentId;
|
|
4364
|
+
return [own ? 0 : 1, credit.createdAt.getTime(), credit.id];
|
|
4365
|
+
};
|
|
4366
|
+
return [...parts].sort((a, b) => {
|
|
4367
|
+
const [x, y] = [rank(a), rank(b)];
|
|
4368
|
+
return x[0] - y[0] || x[1] - y[1] || x[2].localeCompare(y[2], "en");
|
|
4369
|
+
});
|
|
4370
|
+
}
|
|
4371
|
+
function casesCovered(cases, covered) {
|
|
4372
|
+
const limit = toCents(covered);
|
|
4373
|
+
let running = 0;
|
|
4374
|
+
const ids = [];
|
|
4375
|
+
for (const each of cases) {
|
|
4376
|
+
running += toCents(each.amount);
|
|
4377
|
+
if (running <= limit) ids.push(each.id);
|
|
4378
|
+
}
|
|
4379
|
+
return ids;
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
// src/subscriber-reconciliation.types.ts
|
|
4383
|
+
var RECONCILIATION_READ_CAPABILITY = "reconciliation.read";
|
|
4384
|
+
var RECONCILIATION_CLOSE_CAPABILITY = "reconciliation.close";
|
|
4385
|
+
var SUBSCRIBER_PAYMENTS_CAPABILITY = "payments.read";
|
|
4386
|
+
var RECONCILIATION_NOTE_MAX_LENGTH = 500;
|
|
4387
|
+
|
|
4388
|
+
// src/subscriber-reconciliation-mapping.ts
|
|
4389
|
+
var CASES2 = "subscriber_reconciliation_cases";
|
|
4390
|
+
var RECONCILIATION_CASE_KINDS = [
|
|
4391
|
+
"REFUND_UNMATCHED",
|
|
4392
|
+
"REVERSAL_UNMATCHED"
|
|
4393
|
+
];
|
|
4394
|
+
function toSubscriberReconciliationCaseRecord(row) {
|
|
4395
|
+
const where = (column) => ({ table: CASES2, column, id: row.id });
|
|
4396
|
+
const kind = oneOf(RECONCILIATION_CASE_KINDS, row.kind, where("kind"));
|
|
4397
|
+
const aboutRefund = kind === "REFUND_UNMATCHED";
|
|
4398
|
+
if (aboutRefund !== (row.refundId !== null) || aboutRefund === (row.reversalId !== null)) {
|
|
4399
|
+
throw new Error(
|
|
4400
|
+
`${CASES2} row '${row.id}' is a ${kind} naming ${row.refundId === null ? "no" : "a"} refund and ${row.reversalId === null ? "no" : "a"} reversal; a case names what it is about, and nothing else.`
|
|
4401
|
+
);
|
|
4402
|
+
}
|
|
4403
|
+
if (row.closedAt === null !== (row.note === null)) {
|
|
4404
|
+
throw new Error(
|
|
4405
|
+
`${CASES2} row '${row.id}' is ${row.closedAt === null ? "open with" : "closed without"} a note; a case is closed with one, and an open case carries none.`
|
|
4406
|
+
);
|
|
4407
|
+
}
|
|
4408
|
+
if (row.closedAt === null && row.closedBy !== null) {
|
|
4409
|
+
throw new Error(`${CASES2} row '${row.id}' is open and names who closed it.`);
|
|
4410
|
+
}
|
|
4411
|
+
return {
|
|
4412
|
+
id: row.id,
|
|
4413
|
+
subscriberId: row.subscriberId,
|
|
4414
|
+
tenantId: row.tenantId,
|
|
4415
|
+
kind,
|
|
4416
|
+
amount: columnAmountOf(row.amount, where("amount")),
|
|
4417
|
+
currency: row.currency,
|
|
4418
|
+
paymentId: row.paymentId,
|
|
4419
|
+
refundId: row.refundId,
|
|
4420
|
+
reversalId: row.reversalId,
|
|
4421
|
+
gatewayAccount: row.gatewayAccount,
|
|
4422
|
+
gatewayRef: row.gatewayRef,
|
|
4423
|
+
recordedAt: row.recordedAt,
|
|
4424
|
+
closedAt: row.closedAt,
|
|
4425
|
+
closedBy: row.closedBy,
|
|
4426
|
+
note: row.note
|
|
4427
|
+
};
|
|
4428
|
+
}
|
|
4429
|
+
function reconciliationCaseColumns(opened) {
|
|
4430
|
+
const aboutRefund = opened.kind === "REFUND_UNMATCHED";
|
|
4431
|
+
return {
|
|
4432
|
+
subscriberId: opened.payment.subscriberId,
|
|
4433
|
+
tenantId: opened.payment.tenantId,
|
|
4434
|
+
kind: opened.kind,
|
|
4435
|
+
amount: toDecimalString(opened.amount),
|
|
4436
|
+
currency: opened.currency,
|
|
4437
|
+
paymentId: opened.payment.id,
|
|
4438
|
+
refundId: aboutRefund ? opened.about.id : null,
|
|
4439
|
+
reversalId: aboutRefund ? null : opened.about.id,
|
|
4440
|
+
gatewayAccount: opened.about.gatewayAccount,
|
|
4441
|
+
gatewayRef: opened.about.gatewayRef,
|
|
4442
|
+
recordedAt: opened.at,
|
|
4443
|
+
closedAt: null,
|
|
4444
|
+
closedBy: null,
|
|
4445
|
+
note: null
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
var CASE_CLOSED_REFUND_ENDED = "The refund failed or was canceled, and its money came back.";
|
|
4449
|
+
var CASE_CLOSED_REVERSAL_GIVEN_BACK = "The gateway gave the reversal back, and with it what matched nothing.";
|
|
4450
|
+
|
|
4451
|
+
// src/e-invoice-format.ts
|
|
4452
|
+
var CATEGORY = {
|
|
4453
|
+
standard: "S",
|
|
4454
|
+
reduced: "S",
|
|
4455
|
+
exempt: "E",
|
|
4456
|
+
"small-business": "E",
|
|
4457
|
+
"reverse-charge": "AE",
|
|
4458
|
+
"not-taxable": "O"
|
|
4459
|
+
};
|
|
4460
|
+
function vatCategoryOf(kind) {
|
|
4461
|
+
return CATEGORY[kind];
|
|
4462
|
+
}
|
|
4463
|
+
var NOT_XML = /[^\t\n\r\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/gu;
|
|
4464
|
+
function holdsOnlyXmlCharacters(text) {
|
|
4465
|
+
return text.search(NOT_XML) === -1;
|
|
4466
|
+
}
|
|
4467
|
+
var CARRIAGE_RETURN = /\r\n?/g;
|
|
4468
|
+
function asXmlReadsIt(text) {
|
|
4469
|
+
return text.replace(NOT_XML, "?").replace(CARRIAGE_RETURN, "\n");
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
// src/subscriber-mapping.ts
|
|
4473
|
+
function formatCustomerNumber(prefix, sequence) {
|
|
4474
|
+
return `${prefix}${sequence}`;
|
|
4475
|
+
}
|
|
4476
|
+
function toSubscriberRecord(row, tenantId) {
|
|
4477
|
+
return {
|
|
4478
|
+
id: row.id,
|
|
4479
|
+
customerNumber: formatCustomerNumber(row.customerNumberPrefix, row.customerSequence),
|
|
4480
|
+
legalName: row.legalName,
|
|
4481
|
+
addressLine1: row.addressLine1,
|
|
4482
|
+
addressLine2: row.addressLine2,
|
|
4483
|
+
postalCode: row.postalCode,
|
|
4484
|
+
city: row.city,
|
|
4485
|
+
country: row.country,
|
|
4486
|
+
vatId: row.vatId,
|
|
4487
|
+
taxNumber: row.taxNumber,
|
|
4488
|
+
invoiceEmail: row.invoiceEmail,
|
|
4489
|
+
business: row.business,
|
|
4490
|
+
tenantId,
|
|
4491
|
+
migrated: row.migrated,
|
|
4492
|
+
createdAt: row.createdAt,
|
|
4493
|
+
updatedAt: row.updatedAt
|
|
4494
|
+
};
|
|
4495
|
+
}
|
|
4496
|
+
function toSubscriberCorrectionRecord(row) {
|
|
4497
|
+
return {
|
|
4498
|
+
id: row.id,
|
|
4499
|
+
subscriberId: row.subscriberId,
|
|
4500
|
+
previous: toIdentityValues(row.previous),
|
|
4501
|
+
corrected: toIdentityValues(row.corrected),
|
|
4502
|
+
reason: row.reason,
|
|
4503
|
+
correctedBy: row.correctedBy,
|
|
4504
|
+
correctedAt: row.correctedAt
|
|
4505
|
+
};
|
|
4506
|
+
}
|
|
4507
|
+
function toSubscriberTaxOriginChangeRecord(row) {
|
|
4508
|
+
return {
|
|
4509
|
+
id: row.id,
|
|
4510
|
+
subscriberId: row.subscriberId,
|
|
4511
|
+
previous: toTaxOriginValues(row.previous),
|
|
4512
|
+
changed: toTaxOriginValues(row.changed),
|
|
4513
|
+
changedBy: row.changedBy,
|
|
4514
|
+
changedAt: row.changedAt,
|
|
4515
|
+
reason: row.reason
|
|
4516
|
+
};
|
|
4517
|
+
}
|
|
4518
|
+
function identityCorrectionDelta(current, corrected) {
|
|
4519
|
+
const previous = {};
|
|
4520
|
+
const next = {};
|
|
4521
|
+
for (const field of LEGAL_IDENTITY_FIELDS) {
|
|
4522
|
+
const value = corrected[field];
|
|
4523
|
+
if (value === void 0 || value === current[field]) continue;
|
|
4524
|
+
previous[field] = current[field];
|
|
4525
|
+
next[field] = value;
|
|
4526
|
+
}
|
|
4527
|
+
return {
|
|
4528
|
+
previous,
|
|
4529
|
+
corrected: next
|
|
4530
|
+
};
|
|
4531
|
+
}
|
|
4532
|
+
function contractPartiesOf(subscriber, issuer) {
|
|
4533
|
+
const party = {
|
|
4534
|
+
customerNumber: subscriber.customerNumber,
|
|
4535
|
+
legalName: subscriber.legalName,
|
|
4536
|
+
vatId: subscriber.vatId,
|
|
4537
|
+
taxNumber: subscriber.taxNumber,
|
|
4538
|
+
addressLine1: subscriber.addressLine1,
|
|
4539
|
+
addressLine2: subscriber.addressLine2,
|
|
4540
|
+
postalCode: subscriber.postalCode,
|
|
4541
|
+
city: subscriber.city,
|
|
4542
|
+
country: subscriber.country
|
|
4543
|
+
};
|
|
4544
|
+
return {
|
|
4545
|
+
subscriberId: subscriber.id,
|
|
4546
|
+
subscriber: party,
|
|
4547
|
+
issuer: issuer ? issuerPartyOf(issuer) : null
|
|
4548
|
+
};
|
|
4549
|
+
}
|
|
4550
|
+
function issuerPartyOf(issuer) {
|
|
4551
|
+
const identity = issuerIdentityOf(issuer);
|
|
4552
|
+
return {
|
|
4553
|
+
legalName: identity?.legalName ?? "",
|
|
4554
|
+
vatId: identity?.vatId ?? null,
|
|
2660
4555
|
taxNumber: identity?.taxNumber ?? null,
|
|
2661
4556
|
// Settled like the three above rather than copied verbatim: one copy
|
|
2662
4557
|
// whose halves were settled differently is a row that reads two ways,
|
|
@@ -2700,75 +4595,6 @@ function toIdentityValues(value) {
|
|
|
2700
4595
|
return values;
|
|
2701
4596
|
}
|
|
2702
4597
|
|
|
2703
|
-
// src/subscriber-payment-method-mapping.ts
|
|
2704
|
-
var PAYMENT_METHOD_TYPES = ["card", "sepa_debit"];
|
|
2705
|
-
var STATUSES = ["ACTIVE", "REPLACED"];
|
|
2706
|
-
var TABLE3 = "subscriber_payment_methods";
|
|
2707
|
-
function toSubscriberPaymentMethodRecord(row) {
|
|
2708
|
-
return {
|
|
2709
|
-
id: row.id,
|
|
2710
|
-
subscriberId: row.subscriberId,
|
|
2711
|
-
gatewayAccount: row.gatewayAccount,
|
|
2712
|
-
provider: row.provider,
|
|
2713
|
-
customerRef: row.customerRef,
|
|
2714
|
-
paymentMethodRef: row.paymentMethodRef,
|
|
2715
|
-
type: oneOf(PAYMENT_METHOD_TYPES, row.type, { table: TABLE3, column: "type", id: row.id }),
|
|
2716
|
-
brand: row.brand,
|
|
2717
|
-
last4: row.last4,
|
|
2718
|
-
expiryMonth: row.expiryMonth,
|
|
2719
|
-
expiryYear: row.expiryYear,
|
|
2720
|
-
country: row.country,
|
|
2721
|
-
bankCode: row.bankCode,
|
|
2722
|
-
mandateReference: row.mandateReference,
|
|
2723
|
-
status: oneOf(STATUSES, row.status, { table: TABLE3, column: "status", id: row.id }),
|
|
2724
|
-
confirmedAt: row.confirmedAt,
|
|
2725
|
-
replacedAt: row.replacedAt,
|
|
2726
|
-
createdAt: row.createdAt
|
|
2727
|
-
};
|
|
2728
|
-
}
|
|
2729
|
-
function subscriberPaymentMethodColumns(data) {
|
|
2730
|
-
return {
|
|
2731
|
-
subscriberId: data.subscriberId,
|
|
2732
|
-
gatewayAccount: data.gatewayAccount,
|
|
2733
|
-
provider: data.provider,
|
|
2734
|
-
customerRef: data.customerRef,
|
|
2735
|
-
paymentMethodRef: data.paymentMethodRef,
|
|
2736
|
-
type: data.type,
|
|
2737
|
-
brand: data.brand,
|
|
2738
|
-
last4: data.last4,
|
|
2739
|
-
expiryMonth: data.expiryMonth,
|
|
2740
|
-
expiryYear: data.expiryYear,
|
|
2741
|
-
country: data.country,
|
|
2742
|
-
bankCode: data.bankCode,
|
|
2743
|
-
mandateReference: data.mandateReference,
|
|
2744
|
-
confirmedAt: data.confirmedAt
|
|
2745
|
-
};
|
|
2746
|
-
}
|
|
2747
|
-
var FOREIGN_PAYMENT_METHOD_REFERENCE = "FOREIGN_PAYMENT_METHOD_REFERENCE";
|
|
2748
|
-
var ForeignPaymentMethodReferenceError = class extends Error {
|
|
2749
|
-
constructor(gatewayAccount, paymentMethodRef) {
|
|
2750
|
-
super(
|
|
2751
|
-
`Payment method '${paymentMethodRef}' of account '${gatewayAccount}' belongs to another subscriber. A reference belongs to exactly one, so it is not handed out.`
|
|
2752
|
-
);
|
|
2753
|
-
this.gatewayAccount = gatewayAccount;
|
|
2754
|
-
this.paymentMethodRef = paymentMethodRef;
|
|
2755
|
-
this.name = "ForeignPaymentMethodReferenceError";
|
|
2756
|
-
}
|
|
2757
|
-
gatewayAccount;
|
|
2758
|
-
paymentMethodRef;
|
|
2759
|
-
code = FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
2760
|
-
};
|
|
2761
|
-
function isForeignPaymentMethodReferenceError(error) {
|
|
2762
|
-
return error instanceof Error && error.code === FOREIGN_PAYMENT_METHOD_REFERENCE;
|
|
2763
|
-
}
|
|
2764
|
-
function refuseForeignPaymentMethodReference(recorded, subscriberId) {
|
|
2765
|
-
if (recorded.subscriberId === subscriberId) return;
|
|
2766
|
-
throw new ForeignPaymentMethodReferenceError(
|
|
2767
|
-
recorded.gatewayAccount,
|
|
2768
|
-
recorded.paymentMethodRef
|
|
2769
|
-
);
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
4598
|
// src/subscription-bundle-mapping.ts
|
|
2773
4599
|
var BILLING_CYCLES = ["MONTHLY", "YEARLY"];
|
|
2774
4600
|
function toSubscriptionBundleRecord(row) {
|
|
@@ -3118,6 +4944,23 @@ var ERROR_MESSAGES_DE = {
|
|
|
3118
4944
|
BUNDLE_RETIREMENT_REINSTATE_SUBSCRIPTION_ENDS: "Version {version} von {bundleKey} wird stillgelegt, und diese Buchung endet, bevor sie umziehen w\xFCrde. Ihr Abonnement endet bis dahin ebenfalls.",
|
|
3119
4945
|
BUNDLE_RETIREMENT_SWITCH_PLAN_CHANGES: "{bundleName} zieht am {date} auf seine neue Version um, und Ihr Paket \xE4ndert sich vorher. Der Preis, den ein Wechsel halten w\xFCrde, steht deshalb noch nicht fest. Wechseln Sie, sobald Ihr Paket gewechselt hat, oder lassen Sie es an diesem Tag umziehen.",
|
|
3120
4946
|
BUNDLE_VERSION_OFFER_CHANGED: "Das Angebot hat sich seit der Anzeige ge\xE4ndert. Bitte sieh dir das aktuelle an, bevor du wechselst.",
|
|
4947
|
+
FEATURE_WITHDRAWAL_UNAVAILABLE: "Ein Feature zur\xFCckzuziehen ist hier nicht m\xF6glich: Die Speicher k\xF6nnen nicht alle auflisten, die ein R\xFCckzug erreicht.",
|
|
4948
|
+
FEATURE_WITHDRAWAL_FEATURE_UNKNOWN: "Der Katalog kennt kein Feature {featureKey}.",
|
|
4949
|
+
FEATURE_WITHDRAWAL_DATE_IN_PAST: "Ein Feature wird ab jetzt oder ab einem sp\xE4teren Datum zur\xFCckgezogen, nie ab einem vergangenen.",
|
|
4950
|
+
FEATURE_WITHDRAWAL_OPEN: "{featureKey} ist bereits zur\xFCckgezogen, und dieser R\xFCckzug ist nicht aufgehoben. Heben Sie ihn auf, bevor Sie einen weiteren ank\xFCndigen.",
|
|
4951
|
+
FEATURE_WITHDRAWAL_OVERLAPS: "{featureKey} ist bis zum {date} zur\xFCckgezogen. Ein weiterer R\xFCckzug kann fr\xFChestens an diesem Tag beginnen.",
|
|
4952
|
+
FEATURE_WITHDRAWAL_REDUCTION_NOT_REACHED: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} betrifft nichts, was dieser R\xFCckzug erreicht.",
|
|
4953
|
+
FEATURE_WITHDRAWAL_REDUCTION_NAMED_TWICE: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} ist doppelt angegeben.",
|
|
4954
|
+
FEATURE_WITHDRAWAL_REDUCTION_EXCEEDS_PRICE: "Die Minderung f\xFCr {key} bei Abrechnung {billingCycle} ist h\xF6her als der niedrigste Preis, den sie mindert: {price}.",
|
|
4955
|
+
FEATURE_WITHDRAWAL_PREVIEW_CHANGED: "Die Abonnements, die dieser R\xFCckzug erreicht, haben sich seit der Anzeige ge\xE4ndert. Sehen Sie sie erneut an, bevor Sie ihn ank\xFCndigen.",
|
|
4956
|
+
FEATURE_WITHDRAWAL_NOT_FOUND: "R\xFCckzug {withdrawalId} nicht gefunden.",
|
|
4957
|
+
FEATURE_WITHDRAWAL_ALREADY_LIFTED: "Dieser R\xFCckzug ist bereits aufgehoben, ab dem {date}.",
|
|
4958
|
+
FEATURE_WITHDRAWAL_NOT_TOLD_YET: "\xDCber diesen R\xFCckzug ist noch niemand informiert, und er wird nicht aufgehoben, bevor das geschehen ist. Versuchen Sie es gleich noch einmal.",
|
|
4959
|
+
FEATURE_WITHDRAWAL_LIFT_IN_PAST: "Ein R\xFCckzug wird ab jetzt oder ab einem sp\xE4teren Datum aufgehoben, nie ab einem vergangenen.",
|
|
4960
|
+
FEATURE_WITHDRAWAL_NOT_IN_EFFECT: "{featureKey} ist gerade nicht zur\xFCckgezogen. Deshalb kann nichts sofort beendet werden.",
|
|
4961
|
+
FEATURE_WITHDRAWAL_DOES_NOT_REACH: "Dieser R\xFCckzug betrifft Ihr Abonnement nicht, oder diese Buchung nicht mehr.",
|
|
4962
|
+
FEATURE_WITHDRAWAL_ALREADY_ENDED: "Das ist bereits beendet.",
|
|
4963
|
+
FEATURE_WITHDRAWAL_END_NOW_UNSUPPORTED: "Ihre K\xFCndigung zum {date} ist erfasst und kann hier nicht vorgezogen werden. Sie endet an diesem Tag, und bis dahin gilt die Minderung.",
|
|
3121
4964
|
RETIREMENT_SWITCH_NOT_PENDING: "F\xFCr Ihre Version steht keine Einstellung mehr bevor. Es gibt nichts, wohin Sie wechseln k\xF6nnten.",
|
|
3122
4965
|
RETIREMENT_SWITCH_IN_TRIAL: "Der Wechsel ist m\xF6glich, sobald Ihre Testphase endet.",
|
|
3123
4966
|
RETIREMENT_SWITCH_BUNDLE_CANNOT_FOLLOW: "{bundleName} kann im Paket {planName} nicht laufen und l\xE4uft fr\xFChestens bis {until}. Ist es gek\xFCndigt, ist der Wechsel ab diesem Tag m\xF6glich.",
|
|
@@ -3160,6 +5003,23 @@ var ERROR_MESSAGES_DE = {
|
|
|
3160
5003
|
PROMO_MAX_REDEMPTIONS_LOWERED: "maxRedemptions l\xE4sst sich nicht senken.",
|
|
3161
5004
|
// ── contract ──
|
|
3162
5005
|
SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_DECIDED: "Der Vertrag nennt in {field} {stated} %, der Steueradapter entscheidet {decided} %.",
|
|
5006
|
+
SUBSCRIPTION_INVOICE_CHARGE_INVOICED: "Die Forderung '{chargeId}' steht bereits auf einer Rechnung.",
|
|
5007
|
+
SUBSCRIPTION_INVOICE_CONTENT_INCOMPLETE: "Der Rechnung fehlt, was der Steueradapter {adapter} verlangt: {missing}. Sie wird ausgestellt, sobald es erg\xE4nzt ist.",
|
|
5008
|
+
SUBSCRIPTION_INVOICE_PARTIES_UNCONFIRMED: "Der Vertrag {contractId} nennt keinen best\xE4tigten Aussteller und Abonnenten \u2013 eine Migration hat seine Parteien \xFCbernommen, oder er nennt keinen Aussteller \u2013, deshalb werden seine Forderungen nicht in Rechnung gestellt.",
|
|
5009
|
+
SUBSCRIPTION_INVOICE_MANDATE_MISSING: "Die Lastschrift {paymentMethodId} nennt keine Mandatsreferenz, die vor dem Einzug angek\xFCndigt werden muss. Die Rechnung wartet, bis die Zahlungsart ersetzt ist.",
|
|
5010
|
+
SUBSCRIPTION_INVOICE_NO_PAYMENT_METHOD: "Der Abonnent {subscriberId} hat keine Zahlungsart in Gebrauch. Die Rechnung wird eingezogen, sobald er eine hat.",
|
|
5011
|
+
SUBSCRIPTION_INVOICE_FAILURE_NOT_TOLD: "Die Lastschrift wird angek\xFCndigt, sobald dem Abonnenten mitgeteilt ist, dass der Einzug {attemptKey} gescheitert ist.",
|
|
5012
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_CHANGED: "Das Dokument der Rechnung {invoiceId} weicht vom festgehaltenen oder archivierten ab ({rendered} statt {recorded}); das erste bleibt, und der n\xE4chste Lauf kl\xE4rt, welches die Rechnung beh\xE4lt.",
|
|
5013
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_REFUSED: "Der Steueradapter {adapter} weist die gerenderte Rechnung zur\xFCck: {missing}. Die Rechnung beh\xE4lt ihre Nummer und wartet auf ein Dokument, das die Pr\xFCfung besteht.",
|
|
5014
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_MISMATCH: "Das archivierte Dokument der Rechnung {invoiceId} passt nicht zur beim Rendern festgehaltenen Pr\xFCfsumme und wird nicht herausgegeben.",
|
|
5015
|
+
SUBSCRIPTION_INVOICE_DOCUMENT_NOT_ARCHIVED: "Das Dokument der Rechnung {invoiceId} ist noch nicht archiviert.",
|
|
5016
|
+
SUBSCRIPTION_INVOICE_NOT_FOUND: "Keine Rechnung '{invoiceId}'.",
|
|
5017
|
+
SUBSCRIPTION_INVOICE_NO_INVOICE_EMAIL: "Der Abonnent {subscriberId} hat keine Rechnungs-E-Mail; seine Rechnung wird versendet, sobald eine eingetragen ist.",
|
|
5018
|
+
SUBSCRIBER_CREDIT_NOT_FOUND: "Kein Guthaben '{creditId}' des Abonnenten dieses Mandanten.",
|
|
5019
|
+
SUBSCRIBER_PAYOUT_EXCEEDS_CREDIT: "Vom Guthaben {creditId} sind noch {open} offen; mehr wird nicht ausgezahlt, und nichts wurde erfasst.",
|
|
5020
|
+
SUBSCRIBER_PAYOUT_NOT_MADE_YET: "Eine Auszahlung wird erfasst, wenn sie erfolgt ist: {paidOn} liegt nach heute, {today}.",
|
|
5021
|
+
SUBSCRIBER_RECONCILIATION_CASE_NOT_FOUND: "Kein abzugleichender Fall '{caseId}'.",
|
|
5022
|
+
SUBSCRIBER_RECONCILIATION_CASE_CLOSED: "Der Fall {caseId} wurde am {closedAt} geschlossen; seine Notiz bleibt, und nichts wurde erfasst.",
|
|
3163
5023
|
CHECKOUT_OFFER_LINE_ITEMS_REQUIRED: "Aus einem Checkout-Angebot entsteht nur ein einziger Vertrag, und das erst, wenn seine Positionen eingefroren sind.",
|
|
3164
5024
|
CHECKOUT_OFFER_PLAN_LINE_ITEM_REQUIRED: "Ein Checkout-Angebot braucht eine eingefrorene Plan-Position.",
|
|
3165
5025
|
CHECKOUT_OFFER_BUNDLE_LINE_ITEMS_REQUIRED: "Jede gew\xE4hlte Bundle-Version braucht eine eingefrorene Bundle-Position.",
|
|
@@ -3250,16 +5110,37 @@ var ERROR_MESSAGES_DE = {
|
|
|
3250
5110
|
ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES,
|
|
3251
5111
|
AUTH_ERROR_CODES,
|
|
3252
5112
|
BILLING_ERROR_CODES,
|
|
5113
|
+
BLOCKING_COLLECTION_STATUSES,
|
|
3253
5114
|
BUNDLE_PRICE_LOOKUP_LIMIT,
|
|
3254
5115
|
BUNDLE_VERSION_RETIREMENT_CAPABILITY,
|
|
5116
|
+
CASE_CLOSED_REFUND_ENDED,
|
|
5117
|
+
CASE_CLOSED_REVERSAL_GIVEN_BACK,
|
|
3255
5118
|
CATALOGUE_KEYS,
|
|
3256
5119
|
CATALOG_ERROR_CODES,
|
|
5120
|
+
COLLECTION_FAILURE_REASONS,
|
|
5121
|
+
COLLECTION_NOT_SENT_PAID,
|
|
5122
|
+
COLLECTION_NOT_SENT_REVERSED,
|
|
5123
|
+
COLLECTION_RETRY_AFTER_DAYS,
|
|
3257
5124
|
CONTRACT_ERROR_CODES,
|
|
3258
5125
|
CUSTOM_LIMITS_KEYS,
|
|
5126
|
+
DIRECT_DEBIT_LEAD_DAYS,
|
|
5127
|
+
ENDED_REFUND_STATUSES,
|
|
3259
5128
|
ERROR_MESSAGES_DE,
|
|
3260
5129
|
ERROR_MESSAGES_EN,
|
|
3261
5130
|
FEATURE_NOT_LICENSED,
|
|
5131
|
+
FEATURE_WITHDRAWAL_CAPABILITY,
|
|
5132
|
+
FEATURE_WITHDRAWAL_REASON_MAX_LENGTH,
|
|
5133
|
+
FOLLOWED_UP_COLLECTION_STATUSES,
|
|
3262
5134
|
ForeignPaymentMethodReferenceError,
|
|
5135
|
+
GIVING_BACK_CREDIT_ENTRY_KINDS,
|
|
5136
|
+
INVOICE_ARCHIVE_TRIGGERS,
|
|
5137
|
+
INVOICE_COLLECTION_ASK_EVERY_MS,
|
|
5138
|
+
INVOICE_COLLECTION_LEASE_MS,
|
|
5139
|
+
INVOICE_COLLECTION_STATUSES,
|
|
5140
|
+
INVOICE_DOCUMENT_CAPABILITY,
|
|
5141
|
+
INVOICE_ERROR_CODES,
|
|
5142
|
+
INVOICE_SEQUENCE_DIGITS,
|
|
5143
|
+
INVOICE_SETTLEMENT_KINDS,
|
|
3263
5144
|
LEGAL_IDENTITY_FIELDS,
|
|
3264
5145
|
MAINTENANCE_CAPABILITY,
|
|
3265
5146
|
MAINTENANCE_ERROR_CODES,
|
|
@@ -3285,24 +5166,41 @@ var ERROR_MESSAGES_DE = {
|
|
|
3285
5166
|
PersistenceCapabilityError,
|
|
3286
5167
|
PersistenceRefusal,
|
|
3287
5168
|
PlatformUserExistsError,
|
|
5169
|
+
RECONCILIATION_CASE_KINDS,
|
|
5170
|
+
RECONCILIATION_CLOSE_CAPABILITY,
|
|
5171
|
+
RECONCILIATION_NOTE_MAX_LENGTH,
|
|
5172
|
+
RECONCILIATION_READ_CAPABILITY,
|
|
3288
5173
|
REGISTRATION_ERROR_CODES,
|
|
3289
5174
|
REGISTRATION_RESUME_TTL_MINUTES,
|
|
3290
5175
|
REGISTRATION_STEP_BY_STATUS,
|
|
5176
|
+
REOPENING_SETTLEMENT_KINDS,
|
|
3291
5177
|
RETIREMENT_REMINDER_LEAD_DAYS,
|
|
5178
|
+
REVERSIBLE_COLLECTION_STATUSES,
|
|
3292
5179
|
SETTINGS_ERROR_CODES,
|
|
3293
5180
|
SETUP_ERROR_CODES,
|
|
3294
5181
|
SUBSCRIBER_ACCOUNT_CAPABILITY,
|
|
3295
5182
|
SUBSCRIBER_ATTENTION_CAPABILITY,
|
|
3296
5183
|
SUBSCRIBER_ATTENTION_PAGE_SIZE,
|
|
5184
|
+
SUBSCRIBER_CHARGE_ORIGINS,
|
|
5185
|
+
SUBSCRIBER_CHARGE_SOURCES,
|
|
3297
5186
|
SUBSCRIBER_CORRECTION_CAPABILITY,
|
|
5187
|
+
SUBSCRIBER_CREDIT_CAPABILITY,
|
|
5188
|
+
SUBSCRIBER_CREDIT_ENTRY_KINDS,
|
|
3298
5189
|
SUBSCRIBER_ERROR_CODES,
|
|
3299
5190
|
SUBSCRIBER_INVOICE_ADDRESS_FIELDS,
|
|
5191
|
+
SUBSCRIBER_PAYMENTS_CAPABILITY,
|
|
5192
|
+
SUBSCRIBER_PAYOUT_CAPABILITY,
|
|
5193
|
+
SUBSCRIBER_REFUND_STATUSES,
|
|
3300
5194
|
SUBSCRIBER_STANDING_CAPABILITY,
|
|
3301
5195
|
SUBSCRIBER_TAX_ORIGIN_FIELDS,
|
|
3302
5196
|
TAX_ERROR_CODES,
|
|
3303
5197
|
TAX_TREATMENT_KINDS,
|
|
5198
|
+
UNSETTLED_COLLECTION_STATUSES,
|
|
3304
5199
|
VERSION_RETIREMENT_CAPABILITY,
|
|
5200
|
+
addDaysToDay,
|
|
5201
|
+
announcedCollectionDay,
|
|
3305
5202
|
applyPromo,
|
|
5203
|
+
asXmlReadsIt,
|
|
3306
5204
|
assertPersistenceCapabilities,
|
|
3307
5205
|
buildActivePlanVersionWhere,
|
|
3308
5206
|
buildActiveVersionWhere,
|
|
@@ -3312,38 +5210,69 @@ var ERROR_MESSAGES_DE = {
|
|
|
3312
5210
|
bundleStemDefaults,
|
|
3313
5211
|
canonicalJson,
|
|
3314
5212
|
canonicalVatId,
|
|
5213
|
+
casesCovered,
|
|
3315
5214
|
catalogDraftExists,
|
|
3316
5215
|
catalogVersionAlreadyPublished,
|
|
3317
5216
|
catalogVersionGone,
|
|
5217
|
+
chargedAgainOn,
|
|
3318
5218
|
classifyBundleVersionDiff,
|
|
3319
5219
|
classifyIssuerChange,
|
|
3320
5220
|
classifyPlanDiff,
|
|
3321
5221
|
classifyVersionOffer,
|
|
3322
5222
|
collectUnsatisfiedRequires,
|
|
5223
|
+
collectionAttemptKey,
|
|
5224
|
+
collectionOutcomeColumns,
|
|
5225
|
+
collectionStatusOf,
|
|
5226
|
+
columnAmountOf,
|
|
3323
5227
|
computeIncludedVat,
|
|
3324
5228
|
concurrencyGate,
|
|
3325
5229
|
concurrencyGateOf,
|
|
3326
5230
|
contractPartiesOf,
|
|
3327
5231
|
coverageExcludingSelf,
|
|
5232
|
+
coversLifted,
|
|
5233
|
+
coversOf,
|
|
3328
5234
|
customLimitsReader,
|
|
5235
|
+
dayInZone,
|
|
3329
5236
|
definedFields,
|
|
3330
5237
|
diffSettings,
|
|
5238
|
+
directDebitLeadDaysOf,
|
|
5239
|
+
draftSubscriptionInvoice,
|
|
5240
|
+
endedAtOnceSubjectOf,
|
|
5241
|
+
featureWithdrawalRowOf,
|
|
5242
|
+
featuresWithdrawnAt,
|
|
3331
5243
|
formatCustomerNumber,
|
|
3332
5244
|
formatErrorMessage,
|
|
5245
|
+
formatInvoiceNumber,
|
|
3333
5246
|
grossFromNet,
|
|
5247
|
+
holdsOnlyXmlCharacters,
|
|
3334
5248
|
identityCorrectionDelta,
|
|
5249
|
+
inWithdrawalOrder,
|
|
5250
|
+
invoiceArchiveProtectionProblem,
|
|
5251
|
+
invoiceCollectionColumns,
|
|
5252
|
+
invoiceContentDraftOf,
|
|
5253
|
+
invoiceFooterOf,
|
|
5254
|
+
invoiceGroupsOf,
|
|
5255
|
+
invoiceIssuerOf,
|
|
5256
|
+
invoicePaymentOf,
|
|
3335
5257
|
isBundleRedundant,
|
|
5258
|
+
isChargedAgain,
|
|
5259
|
+
isEndedRefund,
|
|
5260
|
+
isFeatureWithdrawnAt,
|
|
3336
5261
|
isForeignPaymentMethodReferenceError,
|
|
5262
|
+
isInvoiceDocumentChecksum,
|
|
3337
5263
|
isMaintenanceAnnouncementLapsed,
|
|
3338
5264
|
isMaintenanceOverrun,
|
|
3339
5265
|
isPaymentCallbackRejectedError,
|
|
3340
5266
|
isPersistenceRefusal,
|
|
3341
5267
|
isPlatformUserExistsError,
|
|
5268
|
+
isRetryableCollectionFailure,
|
|
3342
5269
|
isVersionActiveAt,
|
|
3343
5270
|
isVersionEditable,
|
|
3344
5271
|
issuerIdentityOf,
|
|
3345
5272
|
keepOneRecommended,
|
|
3346
5273
|
keepsVatIdCheck,
|
|
5274
|
+
lastCollectionOf,
|
|
5275
|
+
lastDayInZone,
|
|
3347
5276
|
maintenanceRetryAfterSeconds,
|
|
3348
5277
|
maintenanceStatusOf,
|
|
3349
5278
|
maintenanceWindowStatusOf,
|
|
@@ -3352,14 +5281,22 @@ var ERROR_MESSAGES_DE = {
|
|
|
3352
5281
|
membersLeftOut,
|
|
3353
5282
|
missingRequiresFor,
|
|
3354
5283
|
movedIdentityFields,
|
|
5284
|
+
netByInvoice,
|
|
3355
5285
|
netFromGross,
|
|
5286
|
+
nextWithdrawalChange,
|
|
3356
5287
|
noActivePlanVersion,
|
|
5288
|
+
openOfCredit,
|
|
5289
|
+
owedOn,
|
|
5290
|
+
paidOutByCredit,
|
|
3357
5291
|
parseZonedInstant,
|
|
5292
|
+
paymentSettlement,
|
|
5293
|
+
payoutEntryColumns,
|
|
3358
5294
|
percentOf,
|
|
3359
5295
|
pickActivePromo,
|
|
3360
5296
|
planCatalogSettingsOf,
|
|
3361
5297
|
planKeyTaken,
|
|
3362
5298
|
planNotInCatalog,
|
|
5299
|
+
plannedCollectionColumns,
|
|
3363
5300
|
previousUtcDay,
|
|
3364
5301
|
promoCodeTaken,
|
|
3365
5302
|
promoStatus,
|
|
@@ -3368,54 +5305,90 @@ var ERROR_MESSAGES_DE = {
|
|
|
3368
5305
|
readCustomLimits,
|
|
3369
5306
|
readQuotaRecord,
|
|
3370
5307
|
readQuotaValue,
|
|
5308
|
+
reconciliationCaseColumns,
|
|
3371
5309
|
recordedIssuerIdentity,
|
|
5310
|
+
refundEntryColumns,
|
|
5311
|
+
refundParts,
|
|
3372
5312
|
refuseForeignPaymentMethodReference,
|
|
5313
|
+
refusesFirstAttempt,
|
|
5314
|
+
refusesPlannedAttempt,
|
|
5315
|
+
reinstatementParts,
|
|
3373
5316
|
resolveBundleAvailability,
|
|
3374
5317
|
resolveErrorMessage,
|
|
3375
5318
|
retirementCostsTheSubscription,
|
|
3376
5319
|
retirementReminderDueAt,
|
|
3377
5320
|
retirementReminderIsDue,
|
|
3378
5321
|
retirementSwitchTerms,
|
|
5322
|
+
reversalEntryColumns,
|
|
3379
5323
|
roundToCents,
|
|
3380
5324
|
sameLegalIdentity,
|
|
3381
5325
|
scheduledChangeAfterWrite,
|
|
3382
5326
|
selectChargeableBundles,
|
|
5327
|
+
sentCollectionOf,
|
|
3383
5328
|
settingsSubtreeOf,
|
|
5329
|
+
settledAmountOf,
|
|
3384
5330
|
startOfUtcDay,
|
|
3385
5331
|
subscriberChargeColumns,
|
|
3386
5332
|
subscriberFromRegistration,
|
|
5333
|
+
subscriberPaymentColumns,
|
|
3387
5334
|
subscriberPaymentMethodColumns,
|
|
5335
|
+
subscriberRefundColumns,
|
|
5336
|
+
subscriberReversalColumns,
|
|
3388
5337
|
subscriptionBundleAlreadyCancelled,
|
|
3389
5338
|
subscriptionBundleGone,
|
|
3390
5339
|
subscriptionContractGone,
|
|
3391
5340
|
subscriptionGone,
|
|
5341
|
+
subscriptionInvoiceChargeInvoiced,
|
|
5342
|
+
subscriptionInvoiceColumns,
|
|
5343
|
+
subscriptionInvoiceDocumentChanged,
|
|
5344
|
+
subscriptionInvoiceLineColumns,
|
|
3392
5345
|
sumToCents,
|
|
3393
5346
|
taxOriginOf,
|
|
3394
5347
|
taxOriginWrite,
|
|
5348
|
+
taxPerRate,
|
|
3395
5349
|
taxTreatmentToJson,
|
|
5350
|
+
toArchivedInvoiceDocument,
|
|
3396
5351
|
toBundleStemRow,
|
|
3397
5352
|
toBundleVersionRetirementRecord,
|
|
3398
5353
|
toCents,
|
|
5354
|
+
toCollectionAnnouncedNotice,
|
|
5355
|
+
toCollectionFailedNotice,
|
|
5356
|
+
toCollectionReversedNotice,
|
|
3399
5357
|
toContractLineItemRecord,
|
|
3400
5358
|
toDecimalString,
|
|
5359
|
+
toFeatureWithdrawalRecord,
|
|
5360
|
+
toInvoiceCollectionRecord,
|
|
3401
5361
|
toMaintenanceWindowRecord,
|
|
3402
5362
|
toPlanRow,
|
|
3403
5363
|
toPlanVersionRow,
|
|
3404
5364
|
toRunningContractIssuer,
|
|
3405
5365
|
toSubscriberChargeRecord,
|
|
3406
5366
|
toSubscriberCorrectionRecord,
|
|
5367
|
+
toSubscriberCreditEntryRecord,
|
|
5368
|
+
toSubscriberCreditRecord,
|
|
3407
5369
|
toSubscriberPaymentMethodRecord,
|
|
5370
|
+
toSubscriberPaymentRecord,
|
|
5371
|
+
toSubscriberReconciliationCaseRecord,
|
|
3408
5372
|
toSubscriberRecord,
|
|
5373
|
+
toSubscriberRefundRecord,
|
|
5374
|
+
toSubscriberReversalRecord,
|
|
3409
5375
|
toSubscriberTaxOriginChangeRecord,
|
|
3410
5376
|
toSubscriberVatIdCheckRecord,
|
|
3411
5377
|
toSubscriptionBundleRecord,
|
|
3412
5378
|
toSubscriptionContractRecord,
|
|
5379
|
+
toSubscriptionInvoiceDocumentRecord,
|
|
5380
|
+
toSubscriptionInvoiceRecord,
|
|
3413
5381
|
toSubscriptionNoticeRecord,
|
|
3414
5382
|
toTaxOriginValues,
|
|
3415
5383
|
toTaxTreatment,
|
|
3416
5384
|
toVersionRetirementRecord,
|
|
5385
|
+
utcMidnightOf,
|
|
5386
|
+
vatCategoryOf,
|
|
3417
5387
|
vatIdCheckFromStore,
|
|
3418
5388
|
versionOnSale,
|
|
3419
5389
|
versionOnSaleOrNext,
|
|
3420
|
-
versionSale
|
|
5390
|
+
versionSale,
|
|
5391
|
+
withdrawalParts,
|
|
5392
|
+
withdrawnFeaturesOf,
|
|
5393
|
+
yearOfDay
|
|
3421
5394
|
});
|