@fabriktor/fx 0.0.132 → 0.0.134
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.
|
@@ -48,6 +48,8 @@ export declare class MultiCreatePool {
|
|
|
48
48
|
export type MultiCreateOptions<Input, Result> = {
|
|
49
49
|
run: (input: Input, attempt: MultiCreateAttempt) => Promise<Result>;
|
|
50
50
|
pool?: MultiCreatePool;
|
|
51
|
+
check?: (inputs: readonly Input[], signal: AbortSignal) => Promise<void>;
|
|
52
|
+
on_blocked?: (entry: MultiCreateEntry<Input, Result>) => void;
|
|
51
53
|
retry_same_request?: boolean;
|
|
52
54
|
before_request?: () => void;
|
|
53
55
|
on_failed?: (entry: MultiCreateEntry<Input, Result>) => void;
|
|
@@ -73,6 +75,8 @@ export declare class MultiCreator<Input, Result> {
|
|
|
73
75
|
whenIdle(): Promise<void>;
|
|
74
76
|
cancel(): void;
|
|
75
77
|
private run;
|
|
78
|
+
private reject;
|
|
79
|
+
private settle;
|
|
76
80
|
private replace;
|
|
77
81
|
private publish;
|
|
78
82
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
4
|
// not use this file except in compliance with the License. You may obtain
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
import { isPreActionBlocked } from "./pre-action.js";
|
|
6
7
|
import { createEvent, EventBus, notifyObserver, } from "./events.js";
|
|
7
8
|
export const multiCreateMaximumEntries = 40;
|
|
8
9
|
export const multiCreateConcurrency = 5;
|
|
@@ -138,6 +139,18 @@ export class MultiCreator {
|
|
|
138
139
|
async run(pending) {
|
|
139
140
|
const signal = this.controller.signal;
|
|
140
141
|
const pool = this.opts.pool ?? sharedMultiCreatePool;
|
|
142
|
+
try {
|
|
143
|
+
signal.throwIfAborted();
|
|
144
|
+
this.opts.before_request?.();
|
|
145
|
+
await this.opts.check?.(pending.filter((entry) => !entry.created_id).map((entry) => entry.input), signal);
|
|
146
|
+
signal.throwIfAborted();
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
for (const entry of pending)
|
|
150
|
+
this.reject(entry, error);
|
|
151
|
+
this.settle(pending);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
141
154
|
await Promise.all(pending.map((entry) => pool.run(async () => {
|
|
142
155
|
let current = {
|
|
143
156
|
...entry,
|
|
@@ -175,11 +188,22 @@ export class MultiCreator {
|
|
|
175
188
|
});
|
|
176
189
|
}
|
|
177
190
|
catch (error) {
|
|
178
|
-
current
|
|
179
|
-
this.replace(current);
|
|
180
|
-
notifyObserver(() => this.opts.on_failed?.(current));
|
|
191
|
+
this.reject(current, error);
|
|
181
192
|
}
|
|
182
193
|
})));
|
|
194
|
+
this.settle(pending);
|
|
195
|
+
}
|
|
196
|
+
reject(entry, error) {
|
|
197
|
+
const blocked = isPreActionBlocked(error) && !entry.attempted && !entry.created_id;
|
|
198
|
+
const current = {
|
|
199
|
+
...entry,
|
|
200
|
+
status: blocked ? MultiCreateStatus.Pending : MultiCreateStatus.Failed,
|
|
201
|
+
error,
|
|
202
|
+
};
|
|
203
|
+
this.replace(current);
|
|
204
|
+
notifyObserver(() => blocked ? this.opts.on_blocked?.(current) : this.opts.on_failed?.(current));
|
|
205
|
+
}
|
|
206
|
+
settle(pending) {
|
|
183
207
|
this.running = false;
|
|
184
208
|
this.publish();
|
|
185
209
|
const ids = new Set(pending.map((entry) => entry.id));
|
|
@@ -3,6 +3,9 @@ import { type PaymentsOperator, type BillableOperator, type PayrollsOperator } f
|
|
|
3
3
|
import { type SearchOptions, type OwnerSearchOptions, type SearchPage } from "../core/search.js";
|
|
4
4
|
import { EventBus } from "../core/events.js";
|
|
5
5
|
import type { MultiCreateAttempt, MultiCreateCheckpoint } from "../core/multi-create.js";
|
|
6
|
+
export declare const PaymentsPreActionReason: {
|
|
7
|
+
readonly RecipientNotOnboarded: "recipient_not_onboarded";
|
|
8
|
+
};
|
|
6
9
|
type PurchaseCreditsPayload = Parameters<PaymentsOperator["purchaseCredits"]>[0];
|
|
7
10
|
type OpenPublicSessionPayload = Parameters<PaymentsOperator["openPmtPublicSession"]>[0];
|
|
8
11
|
type PaymentFromPublicSessionPayload = Parameters<PaymentsOperator["paymentFromPublicSession"]>[0];
|
|
@@ -69,6 +72,7 @@ export type PayableEntityBalance = EntityPayments & {
|
|
|
69
72
|
export type SearchPayableEntitiesOptions = SearchOptions & {
|
|
70
73
|
owner_id: string;
|
|
71
74
|
type: PayableEntityType;
|
|
75
|
+
include_pending?: boolean;
|
|
72
76
|
};
|
|
73
77
|
export type ResolvePayableEntityOptions = {
|
|
74
78
|
owner_id: string;
|
|
@@ -191,6 +195,7 @@ export type PaymentsFXOptions = {
|
|
|
191
195
|
events?: EventBus;
|
|
192
196
|
};
|
|
193
197
|
export interface PaymentsFXOperator {
|
|
198
|
+
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<void>;
|
|
194
199
|
resolvePayableEntity(opts: ResolvePayableEntityOptions): Promise<PayableEntityBalance>;
|
|
195
200
|
searchPayableEntities(opts: SearchPayableEntitiesOptions): Promise<SearchPage<PayableEntityBalance>>;
|
|
196
201
|
createEntityPayment(opts: CreateEntityPaymentOptions, attempt: MultiCreateAttempt): Promise<Payment>;
|
|
@@ -243,6 +248,9 @@ export declare class PaymentsFX implements PaymentsFXOperator {
|
|
|
243
248
|
resolveEntityPayments(opts: ResolveEntityPaymentsOptions): Promise<EntityPayments[]>;
|
|
244
249
|
searchPayableEntities(opts: SearchPayableEntitiesOptions): Promise<SearchPage<PayableEntityBalance>>;
|
|
245
250
|
resolvePayableEntity(opts: ResolvePayableEntityOptions): Promise<PayableEntityBalance>;
|
|
251
|
+
private findPayableEntity;
|
|
252
|
+
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<void>;
|
|
253
|
+
private checkPaymentRecipient;
|
|
246
254
|
createEntityPayment(opts: CreateEntityPaymentOptions, attempt: MultiCreateAttempt): Promise<Payment>;
|
|
247
255
|
createPayment(opts: CreatePaymentOptions): Promise<Payment>;
|
|
248
256
|
replacePayment(opts: ReplacePaymentOptions): Promise<Payment>;
|
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
6
|
import { CreditLedgerSourceAndroidIAP, CreditLedgerSourceAppleIAP, CreditLedgerSourceStripe, ZeroID, PaymentKindMain, PaymentEntityTypeBill, PaymentEntityTypePayroll, PSPActionableStatusSucceeded, PSPActionableStatusFailed, PSPActionableStatusCanceled, PSPPaymentStatusPending, PSPPaymentStatusPartial, PSPPaymentStatusFull, } from "@fabriktor/schema";
|
|
7
7
|
import { queryParams, } from "@fabriktor/client";
|
|
8
|
+
import { runPreActionCheck } from "../core/pre-action.js";
|
|
8
9
|
import { errResolveFailed, errValidation } from "../core/err.js";
|
|
9
10
|
import { normalizeIDs, requiredOperationID, resolvedID } from "../core/id.js";
|
|
10
11
|
import { runOwnerSearch, runSearch, } from "../core/search.js";
|
|
11
12
|
import { createEvent, EventBus } from "../core/events.js";
|
|
13
|
+
export const PaymentsPreActionReason = {
|
|
14
|
+
RecipientNotOnboarded: "recipient_not_onboarded",
|
|
15
|
+
};
|
|
12
16
|
const entityPaymentQuerySize = 30;
|
|
13
17
|
const publicAmountPattern = /^\d+$/;
|
|
14
18
|
const resourcePayment = "payment";
|
|
@@ -285,7 +289,8 @@ export class PaymentsFX {
|
|
|
285
289
|
entities,
|
|
286
290
|
signal: opts.signal,
|
|
287
291
|
});
|
|
288
|
-
const results = balances.flatMap((balance, index) => balance.remaining_amount > 0 &&
|
|
292
|
+
const results = balances.flatMap((balance, index) => balance.remaining_amount > 0 &&
|
|
293
|
+
(opts.include_pending || !balance.has_pending)
|
|
289
294
|
? [{ ...balance, entity: entities[index] }]
|
|
290
295
|
: []);
|
|
291
296
|
if (results.length || source.done) {
|
|
@@ -306,6 +311,21 @@ export class PaymentsFX {
|
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
async resolvePayableEntity(opts) {
|
|
314
|
+
const entity = await this.findPayableEntity(opts);
|
|
315
|
+
const [balance] = await this.resolveEntityPayments({
|
|
316
|
+
owner_id: opts.owner_id,
|
|
317
|
+
type: opts.type,
|
|
318
|
+
entities: [entity],
|
|
319
|
+
signal: opts.signal,
|
|
320
|
+
});
|
|
321
|
+
if (!balance)
|
|
322
|
+
throw errResolveFailed({
|
|
323
|
+
resource: resourcePayment,
|
|
324
|
+
msg: msgPaymentHistoryIncomplete,
|
|
325
|
+
});
|
|
326
|
+
return { ...balance, entity };
|
|
327
|
+
}
|
|
328
|
+
async findPayableEntity(opts) {
|
|
309
329
|
opts.signal?.throwIfAborted();
|
|
310
330
|
const record = opts.type === PaymentEntityTypeBill
|
|
311
331
|
? (await this.billable?.findOneBill({
|
|
@@ -323,19 +343,35 @@ export class PaymentsFX {
|
|
|
323
343
|
resource: resourcePayment,
|
|
324
344
|
msg: msgPaymentMissing,
|
|
325
345
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
346
|
+
return payableEntity(record, opts.type, opts.owner_id);
|
|
347
|
+
}
|
|
348
|
+
// Check the whole selection before starting a batch; never retain readiness across actions.
|
|
349
|
+
async checkEntityPaymentRecipients(entities, signal) {
|
|
350
|
+
const recipients = new Set();
|
|
351
|
+
for (const opts of entities) {
|
|
352
|
+
signal?.throwIfAborted();
|
|
353
|
+
const entity = await this.findPayableEntity({ ...opts, signal });
|
|
354
|
+
recipients.add(entity.recipient_id);
|
|
355
|
+
}
|
|
356
|
+
for (const recipient_id of recipients)
|
|
357
|
+
await this.checkPaymentRecipient(recipient_id, signal);
|
|
358
|
+
}
|
|
359
|
+
async checkPaymentRecipient(recipient_id, signal) {
|
|
360
|
+
if (!resolvedID(recipient_id))
|
|
361
|
+
throw errValidation({ msg: msgPaymentEntityInvalid });
|
|
362
|
+
await runPreActionCheck({
|
|
363
|
+
reason: PaymentsPreActionReason.RecipientNotOnboarded,
|
|
364
|
+
allowed: async () => {
|
|
365
|
+
signal?.throwIfAborted();
|
|
366
|
+
const out = await this.payments.onboardingStatus({ recipient_id, signal });
|
|
367
|
+
if (typeof out.value !== "boolean")
|
|
368
|
+
throw errResolveFailed({
|
|
369
|
+
resource: resourcePmtAccount,
|
|
370
|
+
msg: msgPmtAccountMissing,
|
|
371
|
+
});
|
|
372
|
+
return out.value;
|
|
373
|
+
},
|
|
332
374
|
});
|
|
333
|
-
if (!balance)
|
|
334
|
-
throw errResolveFailed({
|
|
335
|
-
resource: resourcePayment,
|
|
336
|
-
msg: msgPaymentHistoryIncomplete,
|
|
337
|
-
});
|
|
338
|
-
return { ...balance, entity };
|
|
339
375
|
}
|
|
340
376
|
async createEntityPayment(opts, attempt) {
|
|
341
377
|
if (attempt.created_id)
|
|
@@ -385,6 +421,11 @@ export class PaymentsFX {
|
|
|
385
421
|
if (opts.created_id)
|
|
386
422
|
return this.findPayment(opts.created_id, opts.signal);
|
|
387
423
|
opts.signal?.throwIfAborted();
|
|
424
|
+
if (!opts.in.is_manual &&
|
|
425
|
+
(opts.in.type === PaymentEntityTypeBill ||
|
|
426
|
+
opts.in.type === PaymentEntityTypePayroll))
|
|
427
|
+
await this.checkPaymentRecipient(opts.in.recipient_id, opts.signal);
|
|
428
|
+
opts.signal?.throwIfAborted();
|
|
388
429
|
opts.before_request?.();
|
|
389
430
|
const out = await this.payments.insertOnePayment(opts);
|
|
390
431
|
const id = requiredOperationID(out, msgCreatedPaymentIDMissing);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabriktor/fx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.134",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"typescript": "^6.0.3"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@fabriktor/client": "0.0.
|
|
20
|
+
"@fabriktor/client": "0.0.94",
|
|
21
21
|
"@fabriktor/fx": "0.0.94",
|
|
22
22
|
"@fabriktor/schema": "0.0.66"
|
|
23
23
|
}
|