@fabriktor/fx 0.0.133 → 0.0.135
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];
|
|
@@ -192,6 +195,7 @@ export type PaymentsFXOptions = {
|
|
|
192
195
|
events?: EventBus;
|
|
193
196
|
};
|
|
194
197
|
export interface PaymentsFXOperator {
|
|
198
|
+
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<void>;
|
|
195
199
|
resolvePayableEntity(opts: ResolvePayableEntityOptions): Promise<PayableEntityBalance>;
|
|
196
200
|
searchPayableEntities(opts: SearchPayableEntitiesOptions): Promise<SearchPage<PayableEntityBalance>>;
|
|
197
201
|
createEntityPayment(opts: CreateEntityPaymentOptions, attempt: MultiCreateAttempt): Promise<Payment>;
|
|
@@ -244,6 +248,9 @@ export declare class PaymentsFX implements PaymentsFXOperator {
|
|
|
244
248
|
resolveEntityPayments(opts: ResolveEntityPaymentsOptions): Promise<EntityPayments[]>;
|
|
245
249
|
searchPayableEntities(opts: SearchPayableEntitiesOptions): Promise<SearchPage<PayableEntityBalance>>;
|
|
246
250
|
resolvePayableEntity(opts: ResolvePayableEntityOptions): Promise<PayableEntityBalance>;
|
|
251
|
+
private findPayableEntity;
|
|
252
|
+
checkEntityPaymentRecipients(entities: readonly ResolvePayableEntityOptions[], signal?: AbortSignal): Promise<void>;
|
|
253
|
+
private checkPaymentRecipient;
|
|
247
254
|
createEntityPayment(opts: CreateEntityPaymentOptions, attempt: MultiCreateAttempt): Promise<Payment>;
|
|
248
255
|
createPayment(opts: CreatePaymentOptions): Promise<Payment>;
|
|
249
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";
|
|
@@ -307,6 +311,21 @@ export class PaymentsFX {
|
|
|
307
311
|
}
|
|
308
312
|
}
|
|
309
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) {
|
|
310
329
|
opts.signal?.throwIfAborted();
|
|
311
330
|
const record = opts.type === PaymentEntityTypeBill
|
|
312
331
|
? (await this.billable?.findOneBill({
|
|
@@ -324,19 +343,35 @@ export class PaymentsFX {
|
|
|
324
343
|
resource: resourcePayment,
|
|
325
344
|
msg: msgPaymentMissing,
|
|
326
345
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
+
},
|
|
333
374
|
});
|
|
334
|
-
if (!balance)
|
|
335
|
-
throw errResolveFailed({
|
|
336
|
-
resource: resourcePayment,
|
|
337
|
-
msg: msgPaymentHistoryIncomplete,
|
|
338
|
-
});
|
|
339
|
-
return { ...balance, entity };
|
|
340
375
|
}
|
|
341
376
|
async createEntityPayment(opts, attempt) {
|
|
342
377
|
if (attempt.created_id)
|
|
@@ -386,6 +421,11 @@ export class PaymentsFX {
|
|
|
386
421
|
if (opts.created_id)
|
|
387
422
|
return this.findPayment(opts.created_id, opts.signal);
|
|
388
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();
|
|
389
429
|
opts.before_request?.();
|
|
390
430
|
const out = await this.payments.insertOnePayment(opts);
|
|
391
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.135",
|
|
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
|
}
|