@pisell/pisellos 2.2.309 → 2.2.310
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/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
- package/dist/modules/BookingContext/index.js +4 -4
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
- package/dist/modules/Customer/index.js +6 -3
- package/dist/modules/Customer/types.d.ts +2 -0
- package/dist/modules/Discount/availability.d.ts +7 -0
- package/dist/modules/Discount/availability.js +49 -0
- package/dist/modules/Discount/entitlementPass.d.ts +34 -0
- package/dist/modules/Discount/entitlementPass.js +147 -0
- package/dist/modules/Discount/index.d.ts +2 -2
- package/dist/modules/Discount/index.js +37 -151
- package/dist/modules/Discount/types.d.ts +36 -5
- package/dist/modules/OpenData/index.d.ts +1 -1
- package/dist/modules/OpenData/index.js +21 -12
- package/dist/modules/Order/index.d.ts +32 -4
- package/dist/modules/Order/index.js +733 -373
- package/dist/modules/Order/types.d.ts +17 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
- package/dist/modules/Order/utils.d.ts +14 -2
- package/dist/modules/Order/utils.js +275 -76
- package/dist/modules/Payment/index.d.ts +0 -5
- package/dist/modules/Payment/index.js +35 -85
- package/dist/modules/Payment/types.d.ts +21 -1
- package/dist/modules/Payment/types.js +2 -0
- package/dist/modules/Payment/walletpass.d.ts +1 -1
- package/dist/modules/Payment/walletpass.js +80 -47
- package/dist/modules/ProductList/index.js +18 -5
- package/dist/modules/Rules/index.d.ts +2 -2
- package/dist/modules/Rules/index.js +480 -186
- package/dist/modules/Rules/types.d.ts +4 -0
- package/dist/server/index.js +8 -5
- package/dist/server/modules/order/index.js +6 -0
- package/dist/server/modules/order/types.d.ts +12 -0
- package/dist/server/modules/payment/index.d.ts +25 -6
- package/dist/server/modules/payment/index.js +660 -133
- package/dist/server/utils/small-ticket.d.ts +8 -5
- package/dist/server/utils/small-ticket.js +1269 -199
- package/dist/solution/BaseSales/index.d.ts +11 -2
- package/dist/solution/BaseSales/index.js +1463 -1173
- package/dist/solution/BaseSales/types.d.ts +7 -3
- package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +12 -3
- package/dist/solution/BookingTicket/index.js +663 -542
- package/dist/solution/BookingTicket/types.d.ts +5 -1
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
- package/dist/solution/ShopDiscount/index.d.ts +2 -0
- package/dist/solution/ShopDiscount/index.js +99 -42
- package/dist/solution/ShopDiscount/utils.d.ts +4 -2
- package/dist/solution/ShopDiscount/utils.js +43 -23
- package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/dist/solution/UnifiedBookingSales/index.js +5 -5
- package/dist/utils/os-private-payload.d.ts +5 -0
- package/dist/utils/os-private-payload.js +17 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
- package/lib/modules/BookingContext/index.js +3 -3
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
- package/lib/modules/Customer/index.js +5 -1
- package/lib/modules/Customer/types.d.ts +2 -0
- package/lib/modules/Discount/availability.d.ts +7 -0
- package/lib/modules/Discount/availability.js +43 -0
- package/lib/modules/Discount/entitlementPass.d.ts +34 -0
- package/lib/modules/Discount/entitlementPass.js +141 -0
- package/lib/modules/Discount/index.d.ts +2 -2
- package/lib/modules/Discount/index.js +23 -129
- package/lib/modules/Discount/types.d.ts +36 -5
- package/lib/modules/OpenData/index.d.ts +1 -1
- package/lib/modules/OpenData/index.js +11 -2
- package/lib/modules/Order/index.d.ts +32 -4
- package/lib/modules/Order/index.js +341 -71
- package/lib/modules/Order/types.d.ts +17 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
- package/lib/modules/Order/utils.d.ts +14 -2
- package/lib/modules/Order/utils.js +186 -3
- package/lib/modules/Payment/index.d.ts +0 -5
- package/lib/modules/Payment/index.js +3 -28
- package/lib/modules/Payment/types.d.ts +21 -1
- package/lib/modules/Payment/types.js +1 -0
- package/lib/modules/Payment/walletpass.d.ts +1 -1
- package/lib/modules/Payment/walletpass.js +29 -9
- package/lib/modules/ProductList/index.js +14 -1
- package/lib/modules/Rules/index.d.ts +2 -2
- package/lib/modules/Rules/index.js +357 -110
- package/lib/modules/Rules/types.d.ts +4 -0
- package/lib/server/index.js +8 -5
- package/lib/server/modules/order/index.js +6 -0
- package/lib/server/modules/order/types.d.ts +12 -0
- package/lib/server/modules/payment/index.d.ts +25 -6
- package/lib/server/modules/payment/index.js +350 -78
- package/lib/server/utils/small-ticket.d.ts +8 -5
- package/lib/server/utils/small-ticket.js +1009 -121
- package/lib/solution/BaseSales/index.d.ts +11 -2
- package/lib/solution/BaseSales/index.js +164 -30
- package/lib/solution/BaseSales/types.d.ts +7 -3
- package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +12 -3
- package/lib/solution/BookingTicket/index.js +103 -41
- package/lib/solution/BookingTicket/types.d.ts +5 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
- package/lib/solution/ShopDiscount/index.d.ts +2 -0
- package/lib/solution/ShopDiscount/index.js +55 -9
- package/lib/solution/ShopDiscount/utils.d.ts +4 -2
- package/lib/solution/ShopDiscount/utils.js +42 -25
- package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/lib/solution/UnifiedBookingSales/index.js +5 -5
- package/lib/utils/os-private-payload.d.ts +5 -0
- package/lib/utils/os-private-payload.js +22 -0
- package/package.json +1 -1
|
@@ -29,6 +29,10 @@ export interface RulesCalcDiscountOptions {
|
|
|
29
29
|
discountId?: number;
|
|
30
30
|
selectedList?: SetDiscountSelectedParams[];
|
|
31
31
|
scan?: boolean;
|
|
32
|
+
/** 本次扫码优先使用的次卡实例;普通重算不传,继续保留商品行当前选择。 */
|
|
33
|
+
preferredEntitlementPassIds?: number[];
|
|
34
|
+
/** 普通重算保留商品行已应用的次卡;最优选择传 false 恢复自动排序。 */
|
|
35
|
+
preserveEntitlementSelection?: boolean;
|
|
32
36
|
/** Change time 重报价目标行:允许已选编辑态折扣卡重新应用到这些 booking 商品。 */
|
|
33
37
|
reapplyBookingDiscountProductUids?: string[];
|
|
34
38
|
/** Reweighed order lines only: reapply selected wallet discounts against their new amount. */
|
package/lib/server/index.js
CHANGED
|
@@ -29,6 +29,7 @@ var _BaseSales = require("../solution/BaseSales");
|
|
|
29
29
|
var _paymentUtils = require("../modules/Order/payment-utils");
|
|
30
30
|
var _salesNotes = require("../modules/Order/salesNotes");
|
|
31
31
|
var _orderCollectionIdentity = require("../modules/Order/utils/orderCollectionIdentity");
|
|
32
|
+
var _osPrivatePayload = require("../utils/os-private-payload");
|
|
32
33
|
var _OpenData = require("../modules/OpenData");
|
|
33
34
|
var _modules = require("./modules");
|
|
34
35
|
Object.keys(_modules).forEach(function (key) {
|
|
@@ -643,7 +644,7 @@ class Server {
|
|
|
643
644
|
} = params;
|
|
644
645
|
const normalizedData = await this.normalizeCheckoutSubmitData(params.data, title);
|
|
645
646
|
const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
|
|
646
|
-
const checkoutData = this.normalizeCheckoutCollections(this.applyCheckoutFulfillmentBuzzer(numberedData, title));
|
|
647
|
+
const checkoutData = this.normalizeCheckoutCollections((0, _osPrivatePayload.stripOsPrivatePayloadFields)(this.applyCheckoutFulfillmentBuzzer(numberedData, title)));
|
|
647
648
|
const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
|
|
648
649
|
const deviceHeaders = await this.buildCheckoutDeviceHeaders(title);
|
|
649
650
|
if (!this.app?.request?.post) {
|
|
@@ -715,11 +716,9 @@ class Server {
|
|
|
715
716
|
return next;
|
|
716
717
|
}
|
|
717
718
|
buildRemoteCheckoutData(data, title) {
|
|
718
|
-
const next = (0,
|
|
719
|
+
const next = (0, _osPrivatePayload.stripOsPrivatePayloadFields)(this.omitCheckoutSyncMode(data));
|
|
719
720
|
if (!next || typeof next !== 'object') return next;
|
|
720
721
|
const record = next;
|
|
721
|
-
delete record[OS_FULFILLMENT_REF_MODE_FIELD];
|
|
722
|
-
delete record[OS_INVOICE_LAYOUT_CONFIG_FIELD];
|
|
723
722
|
this.applyKioskCheckoutOperator(record);
|
|
724
723
|
if (!Array.isArray(record.payments)) return next;
|
|
725
724
|
const originalPayments = record.payments;
|
|
@@ -1297,6 +1296,10 @@ class Server {
|
|
|
1297
1296
|
method: 'get',
|
|
1298
1297
|
path: '/shop/pay/custom-payment/all',
|
|
1299
1298
|
handler: this.handlePaymentMethods.bind(this)
|
|
1299
|
+
}, {
|
|
1300
|
+
method: 'get',
|
|
1301
|
+
path: '/pay/custom-payment/all',
|
|
1302
|
+
handler: this.handlePaymentMethods.bind(this)
|
|
1300
1303
|
}, {
|
|
1301
1304
|
method: 'get',
|
|
1302
1305
|
path: '/shop/quotation/available',
|
|
@@ -4330,7 +4333,7 @@ class Server {
|
|
|
4330
4333
|
}
|
|
4331
4334
|
const normalizedData = await this.normalizeCheckoutSubmitData(data, title);
|
|
4332
4335
|
const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
|
|
4333
|
-
const checkoutData = this.normalizeCheckoutCollections(this.applyCheckoutFulfillmentBuzzer(numberedData, title));
|
|
4336
|
+
const checkoutData = this.normalizeCheckoutCollections((0, _osPrivatePayload.stripOsPrivatePayloadFields)(this.applyCheckoutFulfillmentBuzzer(numberedData, title)));
|
|
4334
4337
|
const pendingResult = await this.handlePendingSyncCheckoutOrder({
|
|
4335
4338
|
backendPath,
|
|
4336
4339
|
data: checkoutData,
|
|
@@ -2036,6 +2036,12 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2036
2036
|
mergedRecord[field] = (0, _lodashEs.cloneDeep)(incomingRecord[field]);
|
|
2037
2037
|
}
|
|
2038
2038
|
|
|
2039
|
+
// order_meta_list 是一次性生成的完整业务快照。incoming 明确携带时
|
|
2040
|
+
// 必须整体覆盖(包括 null / 空对象 / 空数组);省略时才保留 existing。
|
|
2041
|
+
if (Object.prototype.hasOwnProperty.call(incomingRecord, 'order_meta_list')) {
|
|
2042
|
+
mergedRecord.order_meta_list = (0, _lodashEs.cloneDeep)(incomingRecord.order_meta_list);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2039
2045
|
// 子项数组遵循完整快照语义:incoming 自有且为数组(包括 [])时权威替换;
|
|
2040
2046
|
// 字段缺席或不是数组时视为稀疏 patch,继续保留 existing。
|
|
2041
2047
|
for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
|
|
@@ -36,6 +36,8 @@ export type OrderSurchargeType = string;
|
|
|
36
36
|
export interface OrderProductDiscountMetadata {
|
|
37
37
|
/** 设备端本地唯一标识,用于与服务端记录对齐 */
|
|
38
38
|
unique_identification_number?: string;
|
|
39
|
+
/** 月卡扣除本单新增使用后的剩余次数;不限次数时为 null。 */
|
|
40
|
+
balance?: number | null;
|
|
39
41
|
[key: string]: unknown;
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
@@ -452,10 +454,20 @@ export interface OrderData {
|
|
|
452
454
|
id: number | string;
|
|
453
455
|
tag: 'point_card';
|
|
454
456
|
product_id?: number | string;
|
|
457
|
+
code?: string;
|
|
455
458
|
product_name: string;
|
|
456
459
|
par_value: string;
|
|
457
460
|
used_par_value: string;
|
|
458
461
|
}>;
|
|
462
|
+
virtual_currency_snapshot?: Array<{
|
|
463
|
+
id: number | string;
|
|
464
|
+
tag: 'virtual_currency';
|
|
465
|
+
product_id?: number | string;
|
|
466
|
+
product_name: string;
|
|
467
|
+
par_value: string;
|
|
468
|
+
used_par_value: string;
|
|
469
|
+
name: Record<string, string>;
|
|
470
|
+
}>;
|
|
459
471
|
};
|
|
460
472
|
/**
|
|
461
473
|
* 金额汇总(仅输出)。
|
|
@@ -7,22 +7,41 @@ export declare class PaymentServerModule extends BaseModule implements Module {
|
|
|
7
7
|
private request;
|
|
8
8
|
private app;
|
|
9
9
|
private logger;
|
|
10
|
+
private dbManager;
|
|
10
11
|
private store;
|
|
11
|
-
private
|
|
12
|
-
private
|
|
12
|
+
private paymentDataSource;
|
|
13
|
+
private initialLoadInFlight;
|
|
14
|
+
private refreshInFlight;
|
|
15
|
+
private hasLoadedPayMethods;
|
|
16
|
+
private hasEmittedLoaded;
|
|
17
|
+
private syncTimer;
|
|
18
|
+
private pendingMessageUuids;
|
|
19
|
+
private queuedTrailingRefresh;
|
|
20
|
+
private trailingMessageUuids;
|
|
13
21
|
constructor(name?: string, version?: string);
|
|
14
22
|
initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
|
|
23
|
+
preload(): Promise<void>;
|
|
15
24
|
getPayMethodListAsync(options?: {
|
|
16
25
|
shouldFilter?: boolean;
|
|
17
26
|
}): Promise<PaymentMethod[]>;
|
|
18
|
-
|
|
19
|
-
private
|
|
27
|
+
private ensureInitialPayMethods;
|
|
28
|
+
private initPaymentDataSource;
|
|
29
|
+
private setupPaymentMethodSync;
|
|
30
|
+
private schedulePaymentMethodRefresh;
|
|
31
|
+
private refreshPayMethodsSingleFlight;
|
|
32
|
+
private performRemoteRefresh;
|
|
20
33
|
private fetchRemotePayMethods;
|
|
21
|
-
private
|
|
34
|
+
private loadPayMethodsFromSQLite;
|
|
35
|
+
private replacePayMethodsInSQLite;
|
|
36
|
+
private resolveCurrentShopScope;
|
|
37
|
+
private stripSnapshotFields;
|
|
38
|
+
private arePayMethodsEqual;
|
|
22
39
|
private resolvePayMethods;
|
|
23
40
|
private filterPayMethods;
|
|
24
|
-
private
|
|
41
|
+
private setPayMethodMirror;
|
|
42
|
+
private getErrorMessage;
|
|
25
43
|
private logInfo;
|
|
26
44
|
private logWarning;
|
|
27
45
|
private logError;
|
|
46
|
+
destroy(): void;
|
|
28
47
|
}
|
|
@@ -5,18 +5,33 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.PaymentServerModule = void 0;
|
|
7
7
|
var _BaseModule = require("../../../modules/BaseModule");
|
|
8
|
+
var _lodashEs = require("lodash-es");
|
|
8
9
|
const WALLET_PASS_CODE = 'WALLET_PASS';
|
|
9
10
|
const WALLET_DEPENDENT_CODES = new Set(['PRODUCTVOUCHER', 'GIFTCARD', 'POINTCARD']);
|
|
10
11
|
const PAYMENT_METHODS_LOADED_EVENT = 'payment:onPaymentMethodsLoaded';
|
|
12
|
+
const PAYMENT_METHODS_CHANGED_EVENT = 'payment:onPaymentMethodsChanged';
|
|
13
|
+
const PAYMENT_METHOD_STORE_NAME = 'pay_method';
|
|
14
|
+
const CUSTOM_PAYMENT_MODULE = 'custom_payment';
|
|
15
|
+
const PAYMENT_METHOD_SYNC_DEBOUNCE_MS = 500;
|
|
16
|
+
const SNAPSHOT_ORDER_FIELD = '__payment_snapshot_order';
|
|
17
|
+
const SNAPSHOT_SHOP_ID_FIELD = '__payment_snapshot_shop_id';
|
|
11
18
|
class PaymentServerModule extends _BaseModule.BaseModule {
|
|
12
19
|
defaultName = 'payment';
|
|
13
20
|
defaultVersion = '1.0.0';
|
|
14
21
|
request;
|
|
15
22
|
app;
|
|
16
23
|
logger;
|
|
24
|
+
dbManager;
|
|
17
25
|
store;
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
paymentDataSource;
|
|
27
|
+
initialLoadInFlight = null;
|
|
28
|
+
refreshInFlight = null;
|
|
29
|
+
hasLoadedPayMethods = false;
|
|
30
|
+
hasEmittedLoaded = false;
|
|
31
|
+
syncTimer;
|
|
32
|
+
pendingMessageUuids = new Set();
|
|
33
|
+
queuedTrailingRefresh = false;
|
|
34
|
+
trailingMessageUuids = new Set();
|
|
20
35
|
constructor(name, version) {
|
|
21
36
|
super(name || 'payment', version);
|
|
22
37
|
}
|
|
@@ -31,78 +46,327 @@ class PaymentServerModule extends _BaseModule.BaseModule {
|
|
|
31
46
|
const appPlugin = core.getPlugin('app');
|
|
32
47
|
this.app = appPlugin?.getApp();
|
|
33
48
|
this.logger = this.app?.logger;
|
|
49
|
+
this.dbManager = this.app?.sqlite;
|
|
34
50
|
if (!this.request && !this.app?.request) {
|
|
35
51
|
throw new Error('PaymentServerModule 需要 request 插件或 app.request 支持');
|
|
36
52
|
}
|
|
53
|
+
this.initPaymentDataSource();
|
|
54
|
+
this.setupPaymentMethodSync();
|
|
37
55
|
this.logInfo('PaymentServerModule initialized successfully', {
|
|
38
|
-
hasAppRequest: !!this.app?.request
|
|
56
|
+
hasAppRequest: !!this.app?.request,
|
|
57
|
+
hasSQLite: !!this.dbManager,
|
|
58
|
+
hasPaymentDataSource: !!this.paymentDataSource
|
|
39
59
|
});
|
|
40
60
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// });
|
|
51
|
-
// return {
|
|
52
|
-
// code: 200,
|
|
53
|
-
// status: true,
|
|
54
|
-
// data: payMethods,
|
|
55
|
-
// };
|
|
56
|
-
// },
|
|
57
|
-
// },
|
|
58
|
-
// ];
|
|
59
|
-
// }
|
|
60
|
-
|
|
61
|
+
async preload() {
|
|
62
|
+
try {
|
|
63
|
+
await this.ensureInitialPayMethods('preload');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.logError('preload 支付方式失败,保持空列表并允许后续读取重试', {
|
|
66
|
+
error: this.getErrorMessage(error)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
61
70
|
async getPayMethodListAsync(options = {}) {
|
|
62
|
-
this.logInfo('Starting getPayMethodListAsync'
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
this.logInfo('Starting getPayMethodListAsync', {
|
|
72
|
+
hasLoadedPayMethods: this.hasLoadedPayMethods,
|
|
73
|
+
cachedCount: this.store.payMethods.length
|
|
74
|
+
});
|
|
75
|
+
if (!this.hasLoadedPayMethods) {
|
|
76
|
+
try {
|
|
77
|
+
await this.ensureInitialPayMethods('route');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
this.logError('getPayMethodListAsync 初始化失败', {
|
|
80
|
+
error: this.getErrorMessage(error)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
} else if (!this.paymentDataSource) {
|
|
84
|
+
try {
|
|
85
|
+
await this.refreshPayMethodsSingleFlight({
|
|
86
|
+
trigger: 'compat.route',
|
|
87
|
+
emitChanged: false
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.logWarning('兼容模式远端刷新失败,保留当前运行态快照', {
|
|
91
|
+
error: this.getErrorMessage(error)
|
|
92
|
+
});
|
|
93
|
+
}
|
|
65
94
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
95
|
+
return this.resolvePayMethods(this.store.payMethods, options);
|
|
96
|
+
}
|
|
97
|
+
async ensureInitialPayMethods(trigger) {
|
|
98
|
+
if (this.hasLoadedPayMethods) return this.store.payMethods;
|
|
99
|
+
if (this.initialLoadInFlight) return this.initialLoadInFlight;
|
|
100
|
+
this.initialLoadInFlight = (async () => {
|
|
101
|
+
const localSnapshot = await this.loadPayMethodsFromSQLite();
|
|
102
|
+
if (localSnapshot.available) {
|
|
103
|
+
this.setPayMethodMirror(localSnapshot.methods);
|
|
104
|
+
this.logInfo('从 SQLite 加载支付方式兜底快照', {
|
|
105
|
+
trigger,
|
|
106
|
+
count: localSnapshot.methods.length,
|
|
107
|
+
shopScope: this.resolveCurrentShopScope(localSnapshot.methods)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
await this.refreshPayMethodsSingleFlight({
|
|
112
|
+
trigger,
|
|
113
|
+
emitChanged: false
|
|
114
|
+
});
|
|
115
|
+
this.hasLoadedPayMethods = true;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (!localSnapshot.available) throw error;
|
|
118
|
+
this.hasLoadedPayMethods = true;
|
|
119
|
+
this.logWarning('远端刷新失败,继续使用 SQLite 支付方式快照', {
|
|
120
|
+
trigger,
|
|
121
|
+
count: localSnapshot.methods.length,
|
|
122
|
+
error: this.getErrorMessage(error)
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (!this.hasEmittedLoaded) {
|
|
126
|
+
this.hasEmittedLoaded = true;
|
|
127
|
+
await this.core.effects.emit(PAYMENT_METHODS_LOADED_EVENT, this.store.payMethods);
|
|
128
|
+
}
|
|
129
|
+
return this.store.payMethods;
|
|
130
|
+
})().finally(() => {
|
|
131
|
+
this.initialLoadInFlight = null;
|
|
132
|
+
});
|
|
133
|
+
return this.initialLoadInFlight;
|
|
134
|
+
}
|
|
135
|
+
initPaymentDataSource() {
|
|
136
|
+
const PaymentDataSourceClass = this.core.serverOptions?.All_DATA_SOURCES?.PaymentDataSource;
|
|
137
|
+
if (!PaymentDataSourceClass) {
|
|
138
|
+
this.logWarning('PaymentDataSource 不可用,使用无本地持久化的兼容模式');
|
|
139
|
+
return;
|
|
69
140
|
}
|
|
70
|
-
this.
|
|
71
|
-
|
|
141
|
+
this.paymentDataSource = new PaymentDataSourceClass();
|
|
142
|
+
}
|
|
143
|
+
setupPaymentMethodSync() {
|
|
144
|
+
if (!this.paymentDataSource) return;
|
|
145
|
+
this.paymentDataSource.run({
|
|
146
|
+
pubsub: {
|
|
147
|
+
callback: res => {
|
|
148
|
+
const message = res?.data || res;
|
|
149
|
+
if (!message || message.module !== CUSTOM_PAYMENT_MODULE) return;
|
|
150
|
+
const messageUuid = String(message.message_uuid || '').trim();
|
|
151
|
+
if (messageUuid) this.pendingMessageUuids.add(messageUuid);
|
|
152
|
+
this.logInfo('收到 custom_payment Ably 消息,等待全量刷新', {
|
|
153
|
+
message_uuid: messageUuid || undefined,
|
|
154
|
+
action: message.action,
|
|
155
|
+
operation: message.operation
|
|
156
|
+
});
|
|
157
|
+
this.schedulePaymentMethodRefresh();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}).catch(error => {
|
|
161
|
+
this.logError('建立 custom_payment pubsub 订阅失败', {
|
|
162
|
+
error: this.getErrorMessage(error)
|
|
163
|
+
});
|
|
72
164
|
});
|
|
73
|
-
const payMethods = await this.payMethodListInFlight;
|
|
74
|
-
return this.resolvePayMethods(payMethods, options);
|
|
75
165
|
}
|
|
76
|
-
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
166
|
+
schedulePaymentMethodRefresh() {
|
|
167
|
+
if (this.syncTimer) clearTimeout(this.syncTimer);
|
|
168
|
+
this.syncTimer = setTimeout(() => {
|
|
169
|
+
this.syncTimer = undefined;
|
|
170
|
+
const messageUuids = [...this.pendingMessageUuids];
|
|
171
|
+
this.pendingMessageUuids.clear();
|
|
172
|
+
void this.refreshPayMethodsSingleFlight({
|
|
173
|
+
trigger: 'ably',
|
|
174
|
+
emitChanged: true,
|
|
175
|
+
messageUuids
|
|
176
|
+
}).catch(error => {
|
|
177
|
+
this.logError('custom_payment Ably 全量刷新失败,保留旧快照', {
|
|
178
|
+
message_uuids: messageUuids,
|
|
179
|
+
error: this.getErrorMessage(error)
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}, PAYMENT_METHOD_SYNC_DEBOUNCE_MS);
|
|
79
183
|
}
|
|
80
|
-
async
|
|
184
|
+
async refreshPayMethodsSingleFlight(context) {
|
|
185
|
+
if (this.refreshInFlight) {
|
|
186
|
+
if (context.trigger.startsWith('ably')) {
|
|
187
|
+
this.queuedTrailingRefresh = true;
|
|
188
|
+
context.messageUuids?.forEach(uuid => this.trailingMessageUuids.add(uuid));
|
|
189
|
+
this.logInfo('支付方式刷新进行中,已排队一次尾随刷新', {
|
|
190
|
+
trigger: context.trigger,
|
|
191
|
+
message_uuids: context.messageUuids || []
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return this.refreshInFlight;
|
|
195
|
+
}
|
|
196
|
+
const refreshTask = this.performRemoteRefresh(context);
|
|
197
|
+
this.refreshInFlight = refreshTask;
|
|
81
198
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.
|
|
86
|
-
|
|
199
|
+
return await refreshTask;
|
|
200
|
+
} finally {
|
|
201
|
+
if (this.refreshInFlight === refreshTask) this.refreshInFlight = null;
|
|
202
|
+
if (this.queuedTrailingRefresh) {
|
|
203
|
+
this.queuedTrailingRefresh = false;
|
|
204
|
+
const messageUuids = [...this.trailingMessageUuids];
|
|
205
|
+
this.trailingMessageUuids.clear();
|
|
206
|
+
void this.refreshPayMethodsSingleFlight({
|
|
207
|
+
trigger: 'ably.trailing',
|
|
208
|
+
emitChanged: true,
|
|
209
|
+
messageUuids
|
|
210
|
+
}).catch(error => {
|
|
211
|
+
this.logError('custom_payment 尾随刷新失败,保留当前快照', {
|
|
212
|
+
message_uuids: messageUuids,
|
|
213
|
+
error: this.getErrorMessage(error)
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async performRemoteRefresh(context) {
|
|
220
|
+
const oldMethods = this.store.payMethods;
|
|
221
|
+
const payMethods = await this.fetchRemotePayMethods();
|
|
222
|
+
const persisted = await this.replacePayMethodsInSQLite(payMethods);
|
|
223
|
+
if (!persisted) {
|
|
224
|
+
this.logWarning('SQLite 不可用,支付方式仅用于当前运行时', {
|
|
225
|
+
trigger: context.trigger,
|
|
226
|
+
count: payMethods.length
|
|
87
227
|
});
|
|
88
|
-
return payMethods;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('[PaymentServerModule] 获取支付方式列表失败', error);
|
|
91
|
-
this.logError('getPayMethodListAsync failed', error);
|
|
92
|
-
return [];
|
|
93
228
|
}
|
|
229
|
+
this.setPayMethodMirror(payMethods);
|
|
230
|
+
this.hasLoadedPayMethods = true;
|
|
231
|
+
const changed = !this.arePayMethodsEqual(oldMethods, payMethods);
|
|
232
|
+
if (context.emitChanged && changed) {
|
|
233
|
+
await this.core.effects.emit(PAYMENT_METHODS_CHANGED_EVENT, {
|
|
234
|
+
oldMethods,
|
|
235
|
+
newMethods: this.store.payMethods
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
this.logInfo('远端支付方式全量刷新成功', {
|
|
239
|
+
trigger: context.trigger,
|
|
240
|
+
message_uuids: context.messageUuids || [],
|
|
241
|
+
count: payMethods.length,
|
|
242
|
+
persisted,
|
|
243
|
+
changed
|
|
244
|
+
});
|
|
245
|
+
return this.store.payMethods;
|
|
94
246
|
}
|
|
95
247
|
async fetchRemotePayMethods() {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
248
|
+
const response = this.paymentDataSource ? await this.paymentDataSource.run({
|
|
249
|
+
http: {
|
|
250
|
+
url: '/shop/pay/custom-payment/all'
|
|
251
|
+
}
|
|
252
|
+
}) : await (this.app?.request || this.request).get('/shop/pay/custom-payment/all', undefined);
|
|
253
|
+
const payMethods = response?.data !== undefined ? response.data : response;
|
|
254
|
+
if (!Array.isArray(payMethods)) {
|
|
255
|
+
throw new Error('支付方式全量接口返回值不是数组');
|
|
256
|
+
}
|
|
257
|
+
if (payMethods.some(method => !method || method.id === undefined || method.id === null)) {
|
|
258
|
+
throw new Error('支付方式全量接口存在缺少 id 的记录');
|
|
259
|
+
}
|
|
260
|
+
return payMethods;
|
|
261
|
+
}
|
|
262
|
+
async loadPayMethodsFromSQLite() {
|
|
263
|
+
if (!this.dbManager) return {
|
|
264
|
+
available: false,
|
|
265
|
+
methods: []
|
|
266
|
+
};
|
|
267
|
+
try {
|
|
268
|
+
const rows = await this.dbManager.getAll(PAYMENT_METHOD_STORE_NAME);
|
|
269
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
270
|
+
return {
|
|
271
|
+
available: false,
|
|
272
|
+
methods: []
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const currentShopScope = this.resolveCurrentShopScope();
|
|
276
|
+
if (!currentShopScope) {
|
|
277
|
+
this.logWarning('无法确认当前店铺,忽略 SQLite 支付方式快照');
|
|
278
|
+
return {
|
|
279
|
+
available: false,
|
|
280
|
+
methods: []
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const scopedRows = rows.filter(row => String(row?.[SNAPSHOT_SHOP_ID_FIELD] || '') === currentShopScope);
|
|
284
|
+
if (scopedRows.length === 0) {
|
|
285
|
+
this.logWarning('忽略其他店铺的 SQLite 支付方式快照', {
|
|
286
|
+
currentShopScope,
|
|
287
|
+
storedShopScopes: [...new Set(rows.map(row => String(row?.[SNAPSHOT_SHOP_ID_FIELD] || '')).filter(Boolean))]
|
|
288
|
+
});
|
|
289
|
+
return {
|
|
290
|
+
available: false,
|
|
291
|
+
methods: []
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const methods = scopedRows.slice().sort((left, right) => Number(left?.[SNAPSHOT_ORDER_FIELD] || 0) - Number(right?.[SNAPSHOT_ORDER_FIELD] || 0)).map(row => this.stripSnapshotFields(row));
|
|
295
|
+
return {
|
|
296
|
+
available: true,
|
|
297
|
+
methods
|
|
298
|
+
};
|
|
299
|
+
} catch (error) {
|
|
300
|
+
this.logWarning('从 SQLite 读取支付方式失败', {
|
|
301
|
+
error: this.getErrorMessage(error)
|
|
302
|
+
});
|
|
303
|
+
return {
|
|
304
|
+
available: false,
|
|
305
|
+
methods: []
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async replacePayMethodsInSQLite(payMethods) {
|
|
310
|
+
if (!this.dbManager) return false;
|
|
311
|
+
const previousRows = await this.dbManager.getAll(PAYMENT_METHOD_STORE_NAME);
|
|
312
|
+
const shopScope = this.resolveCurrentShopScope(payMethods);
|
|
313
|
+
const snapshotRows = payMethods.map((method, index) => ({
|
|
314
|
+
...method,
|
|
315
|
+
[SNAPSHOT_ORDER_FIELD]: index,
|
|
316
|
+
[SNAPSHOT_SHOP_ID_FIELD]: shopScope
|
|
317
|
+
}));
|
|
318
|
+
try {
|
|
319
|
+
await this.dbManager.clear(PAYMENT_METHOD_STORE_NAME);
|
|
320
|
+
if (snapshotRows.length > 0) {
|
|
321
|
+
await this.dbManager.bulkAdd(PAYMENT_METHOD_STORE_NAME, snapshotRows);
|
|
322
|
+
}
|
|
323
|
+
return true;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const writeError = this.getErrorMessage(error);
|
|
326
|
+
try {
|
|
327
|
+
await this.dbManager.clear(PAYMENT_METHOD_STORE_NAME);
|
|
328
|
+
if (Array.isArray(previousRows) && previousRows.length > 0) {
|
|
329
|
+
await this.dbManager.bulkAdd(PAYMENT_METHOD_STORE_NAME, previousRows);
|
|
330
|
+
}
|
|
331
|
+
} catch (rollbackError) {
|
|
332
|
+
this.logError('支付方式 SQLite 快照回滚失败', {
|
|
333
|
+
writeError,
|
|
334
|
+
rollbackError: this.getErrorMessage(rollbackError)
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
throw new Error(`支付方式 SQLite 全量替换失败: ${writeError}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
resolveCurrentShopScope(payMethods = []) {
|
|
341
|
+
const readStorageObject = key => {
|
|
342
|
+
try {
|
|
343
|
+
const raw = this.app?.storage?.getStorage?.(key);
|
|
344
|
+
if (!raw) return {};
|
|
345
|
+
if (typeof raw === 'object') return raw;
|
|
346
|
+
const parsed = JSON.parse(raw);
|
|
347
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
348
|
+
} catch {
|
|
349
|
+
return {};
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const selectedStore = readStorageObject('selectedStore');
|
|
353
|
+
const core = readStorageObject('core');
|
|
354
|
+
const scope = selectedStore.id ?? selectedStore.shop_id ?? core.id ?? core.shop_id ?? payMethods.find(method => method?.shop_id != null)?.shop_id;
|
|
355
|
+
return scope === undefined || scope === null ? '' : String(scope);
|
|
356
|
+
}
|
|
357
|
+
stripSnapshotFields(row) {
|
|
358
|
+
const {
|
|
359
|
+
[SNAPSHOT_ORDER_FIELD]: _snapshotOrder,
|
|
360
|
+
[SNAPSHOT_SHOP_ID_FIELD]: _snapshotShopId,
|
|
361
|
+
...method
|
|
362
|
+
} = row;
|
|
363
|
+
return method;
|
|
100
364
|
}
|
|
101
|
-
|
|
102
|
-
return
|
|
365
|
+
arePayMethodsEqual(left, right) {
|
|
366
|
+
return (0, _lodashEs.isEqual)(left, right);
|
|
103
367
|
}
|
|
104
368
|
resolvePayMethods(payMethods, options) {
|
|
105
|
-
if (!options.shouldFilter) return payMethods;
|
|
369
|
+
if (!options.shouldFilter) return [...payMethods];
|
|
106
370
|
return this.filterPayMethods(payMethods);
|
|
107
371
|
}
|
|
108
372
|
filterPayMethods(payMethods) {
|
|
@@ -121,48 +385,56 @@ class PaymentServerModule extends _BaseModule.BaseModule {
|
|
|
121
385
|
});
|
|
122
386
|
return availableMethods;
|
|
123
387
|
}
|
|
124
|
-
|
|
125
|
-
this.
|
|
126
|
-
|
|
388
|
+
setPayMethodMirror(payMethods) {
|
|
389
|
+
this.store.payMethods = payMethods.map(method => ({
|
|
390
|
+
...method
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
getErrorMessage(error) {
|
|
394
|
+
return error instanceof Error ? error.message : String(error);
|
|
127
395
|
}
|
|
128
396
|
logInfo(title, metadata) {
|
|
129
397
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
}
|
|
398
|
+
this.logger?.addLog({
|
|
399
|
+
type: 'info',
|
|
400
|
+
title: `[PaymentServerModule] ${title}`,
|
|
401
|
+
metadata: metadata || {}
|
|
402
|
+
});
|
|
137
403
|
} catch {
|
|
138
404
|
// 日志记录失败不影响主流程
|
|
139
405
|
}
|
|
140
406
|
}
|
|
141
407
|
logWarning(title, metadata) {
|
|
142
408
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
}
|
|
409
|
+
this.logger?.addLog({
|
|
410
|
+
type: 'warning',
|
|
411
|
+
title: `[PaymentServerModule] ${title}`,
|
|
412
|
+
metadata: metadata || {}
|
|
413
|
+
});
|
|
150
414
|
} catch {
|
|
151
415
|
// 日志记录失败不影响主流程
|
|
152
416
|
}
|
|
153
417
|
}
|
|
154
418
|
logError(title, metadata) {
|
|
155
419
|
try {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
162
|
-
}
|
|
420
|
+
this.logger?.addLog({
|
|
421
|
+
type: 'error',
|
|
422
|
+
title: `[PaymentServerModule] ${title}`,
|
|
423
|
+
metadata: metadata || {}
|
|
424
|
+
});
|
|
163
425
|
} catch {
|
|
164
426
|
// 日志记录失败不影响主流程
|
|
165
427
|
}
|
|
166
428
|
}
|
|
429
|
+
destroy() {
|
|
430
|
+
if (this.syncTimer) clearTimeout(this.syncTimer);
|
|
431
|
+
this.syncTimer = undefined;
|
|
432
|
+
this.pendingMessageUuids.clear();
|
|
433
|
+
this.trailingMessageUuids.clear();
|
|
434
|
+
this.queuedTrailingRefresh = false;
|
|
435
|
+
this.paymentDataSource?.destroy?.();
|
|
436
|
+
this.paymentDataSource = undefined;
|
|
437
|
+
super.destroy();
|
|
438
|
+
}
|
|
167
439
|
}
|
|
168
440
|
exports.PaymentServerModule = PaymentServerModule;
|