@pisell/pisellos 2.3.154 → 2.3.156
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/promotion/index.js +0 -9
- package/dist/modules/Order/index.d.ts +1 -1
- package/dist/modules/Order/index.js +54 -31
- package/dist/modules/Order/localEftposState.d.ts +13 -0
- package/dist/modules/Order/localEftposState.js +39 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1460 -1295
- package/dist/server/modules/order/index.d.ts +13 -0
- package/dist/server/modules/order/index.js +670 -518
- package/dist/solution/BaseSales/index.d.ts +6 -0
- package/dist/solution/BaseSales/index.js +871 -622
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/lib/modules/Order/index.d.ts +1 -1
- package/lib/modules/Order/index.js +15 -1
- package/lib/modules/Order/localEftposState.d.ts +13 -0
- package/lib/modules/Order/localEftposState.js +44 -0
- package/lib/server/index.d.ts +1 -0
- package/lib/server/index.js +73 -5
- package/lib/server/modules/order/index.d.ts +13 -0
- package/lib/server/modules/order/index.js +78 -3
- package/lib/solution/BaseSales/index.d.ts +6 -0
- package/lib/solution/BaseSales/index.js +129 -4
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -328,7 +328,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
|
|
|
328
328
|
date: string;
|
|
329
329
|
status: string;
|
|
330
330
|
week: string;
|
|
331
|
-
weekNum: 0 |
|
|
331
|
+
weekNum: 0 | 5 | 3 | 1 | 2 | 4 | 6;
|
|
332
332
|
}[]>;
|
|
333
333
|
submitTimeSlot(timeSlots: TimeSliceItem): void;
|
|
334
334
|
private getScheduleDataByIds;
|
|
@@ -595,7 +595,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
|
|
|
595
595
|
generateIdempotencyToken(): string;
|
|
596
596
|
syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
|
|
597
597
|
createOrder(params: CommitOrderParams['query']): {
|
|
598
|
-
type: "
|
|
598
|
+
type: "appointment_booking" | "virtual";
|
|
599
599
|
platform: string;
|
|
600
600
|
sales_channel: string;
|
|
601
601
|
order_sales_channel: string;
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "isFulfillmentTimingBooking", {
|
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
13
|
var _loadSalesOrderSnapshot = require("../../shared/sales/loadSalesOrderSnapshot");
|
|
14
|
+
var _localEftposState = require("./localEftposState");
|
|
14
15
|
var _BaseModule = require("../BaseModule");
|
|
15
16
|
var _types = require("./types");
|
|
16
17
|
var _utils = require("./utils");
|
|
@@ -3173,6 +3174,18 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
3173
3174
|
const matchMode = options.matchBy === 'compat' || options.matchBy === 'legacy' ? 'compat' : 'payment_number';
|
|
3174
3175
|
const matchedPayment = (0, _paymentUtils.resolveOrderPaymentIdentity)(tempOrder.payments || [], identity, matchMode);
|
|
3175
3176
|
if (!matchedPayment) {
|
|
3177
|
+
try {
|
|
3178
|
+
void Promise.resolve(this.logger?.addLog?.({
|
|
3179
|
+
type: 'warning',
|
|
3180
|
+
title: '[PaymentTrace] payment-missing',
|
|
3181
|
+
metadata: {
|
|
3182
|
+
moduleName: this.name,
|
|
3183
|
+
saleId: tempOrder.external_sale_number,
|
|
3184
|
+
paymentNumber: identity,
|
|
3185
|
+
currentPaymentNumbers: (tempOrder.payments || []).map(p => p.payment_number)
|
|
3186
|
+
}
|
|
3187
|
+
})).catch(() => {});
|
|
3188
|
+
} catch {/* 日志失败不影响原异常。 */}
|
|
3176
3189
|
throw new Error(`未找到支付项: ${identity}`);
|
|
3177
3190
|
}
|
|
3178
3191
|
let updatedPayment = null;
|
|
@@ -4716,6 +4729,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
4716
4729
|
}
|
|
4717
4730
|
async runSubmitTempOrder(params) {
|
|
4718
4731
|
const tempOrder = this.ensureTempOrder();
|
|
4732
|
+
if ((0, _localEftposState.requiresLocalPaymentRefresh)(tempOrder)) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
|
|
4719
4733
|
this.prepareVirtualSettlementTempOrder(tempOrder);
|
|
4720
4734
|
const isOnlineStorePlatform = this.isOnlineStorePlatform();
|
|
4721
4735
|
const shouldConfirmPendingVoucherPayments = params?.confirmPendingVoucherPayments !== false;
|
|
@@ -5190,7 +5204,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
5190
5204
|
query
|
|
5191
5205
|
} = params;
|
|
5192
5206
|
const fetchUrl = url || '/order/sales/checkout';
|
|
5193
|
-
const requestQuery = this.isSalesCheckoutHandledByOsServer(fetchUrl) ? query : (0, _osPrivatePayload.stripOsPrivatePayloadFields)(query);
|
|
5207
|
+
const requestQuery = this.isSalesCheckoutHandledByOsServer(fetchUrl) ? query : (0, _localEftposState.stripLocalEftposState)((0, _osPrivatePayload.stripOsPrivatePayloadFields)(query));
|
|
5194
5208
|
return this.request.post(fetchUrl, requestQuery, {
|
|
5195
5209
|
osServer: true,
|
|
5196
5210
|
customToast: () => {}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Local reconciliation state; never part of a cloud checkout payload. */
|
|
2
|
+
export declare const LOCAL_EFTPOS_REFRESH_REQUIRED = "_local_eftpos_refresh_required";
|
|
3
|
+
export declare const LOCAL_EFTPOS_FINALIZED = "_local_eftpos_finalized";
|
|
4
|
+
export declare const PAYMENT_REFRESH_MESSAGE = "\u8BA2\u5355\u9700\u8981\u5237\u65B0\uFF0C\u8BF7\u8054\u7F51\u5237\u65B0\u8BA2\u5355\u540E\u518D\u4ED8\u6B3E";
|
|
5
|
+
export interface LocalEftposFinalization {
|
|
6
|
+
saleId: string | number;
|
|
7
|
+
paymentNumber: string;
|
|
8
|
+
result: 'failed' | 'not_started';
|
|
9
|
+
failedTransactionNumbers: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function requiresLocalPaymentRefresh(order: any): boolean;
|
|
12
|
+
export declare function isLocalFinalizedPayment(payment: any): boolean;
|
|
13
|
+
export declare function stripLocalEftposState<T extends Record<string, any>>(order: T): T;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.PAYMENT_REFRESH_MESSAGE = exports.LOCAL_EFTPOS_REFRESH_REQUIRED = exports.LOCAL_EFTPOS_FINALIZED = void 0;
|
|
7
|
+
exports.isLocalFinalizedPayment = isLocalFinalizedPayment;
|
|
8
|
+
exports.requiresLocalPaymentRefresh = requiresLocalPaymentRefresh;
|
|
9
|
+
exports.stripLocalEftposState = stripLocalEftposState;
|
|
10
|
+
/** Local reconciliation state; never part of a cloud checkout payload. */
|
|
11
|
+
const LOCAL_EFTPOS_REFRESH_REQUIRED = exports.LOCAL_EFTPOS_REFRESH_REQUIRED = '_local_eftpos_refresh_required';
|
|
12
|
+
const LOCAL_EFTPOS_FINALIZED = exports.LOCAL_EFTPOS_FINALIZED = '_local_eftpos_finalized';
|
|
13
|
+
const PAYMENT_REFRESH_MESSAGE = exports.PAYMENT_REFRESH_MESSAGE = '订单需要刷新,请联网刷新订单后再付款';
|
|
14
|
+
function requiresLocalPaymentRefresh(order) {
|
|
15
|
+
return order?.metadata?.[LOCAL_EFTPOS_REFRESH_REQUIRED] === true;
|
|
16
|
+
}
|
|
17
|
+
function isLocalFinalizedPayment(payment) {
|
|
18
|
+
return payment?.status === 'voided' && payment?.metadata?.[LOCAL_EFTPOS_FINALIZED] === true;
|
|
19
|
+
}
|
|
20
|
+
function stripLocalEftposState(order) {
|
|
21
|
+
const result = {
|
|
22
|
+
...order
|
|
23
|
+
};
|
|
24
|
+
if (order.metadata) {
|
|
25
|
+
result.metadata = {
|
|
26
|
+
...order.metadata
|
|
27
|
+
};
|
|
28
|
+
delete result.metadata[LOCAL_EFTPOS_REFRESH_REQUIRED];
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(order.payments)) {
|
|
31
|
+
result.payments = order.payments.filter(payment => !isLocalFinalizedPayment(payment)).map(payment => {
|
|
32
|
+
if (!payment.metadata?.[LOCAL_EFTPOS_FINALIZED]) return payment;
|
|
33
|
+
const metadata = {
|
|
34
|
+
...payment.metadata
|
|
35
|
+
};
|
|
36
|
+
delete metadata[LOCAL_EFTPOS_FINALIZED];
|
|
37
|
+
return {
|
|
38
|
+
...payment,
|
|
39
|
+
metadata
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
package/lib/server/index.d.ts
CHANGED
|
@@ -499,6 +499,7 @@ declare class Server {
|
|
|
499
499
|
private buildLocalPaymentSurchargeUpdate;
|
|
500
500
|
private handleGetLocalPayment;
|
|
501
501
|
private handleUpdateLocalPayment;
|
|
502
|
+
private handleFinalizeLocalEftposPayment;
|
|
502
503
|
private runLocalPaymentUpdateInQueue;
|
|
503
504
|
private processLocalPaymentUpdate;
|
|
504
505
|
private processSuccessfulLocalPayment;
|
package/lib/server/index.js
CHANGED
|
@@ -7,6 +7,7 @@ var _exportNames = {};
|
|
|
7
7
|
exports.default = void 0;
|
|
8
8
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
9
9
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
10
|
+
var _localEftposState = require("../modules/Order/localEftposState");
|
|
10
11
|
var _lodashEs = require("lodash-es");
|
|
11
12
|
var _products = require("./modules/products");
|
|
12
13
|
var _menu = require("./modules/menu");
|
|
@@ -646,7 +647,7 @@ class Server {
|
|
|
646
647
|
const normalizedData = await this.normalizeCheckoutSubmitData(params.data, title);
|
|
647
648
|
const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
|
|
648
649
|
const checkoutData = this.normalizeCheckoutCollections((0, _osPrivatePayload.stripOsPrivatePayloadFields)(this.applyCheckoutFulfillmentBuzzer(numberedData, title)));
|
|
649
|
-
const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
|
|
650
|
+
const remoteCheckoutData = (0, _localEftposState.stripLocalEftposState)(this.buildRemoteCheckoutData(checkoutData, title));
|
|
650
651
|
const deviceHeaders = await this.buildCheckoutDeviceHeaders(title);
|
|
651
652
|
if (!this.app?.request?.post) {
|
|
652
653
|
throw new Error('app.request 不可用');
|
|
@@ -784,7 +785,7 @@ class Server {
|
|
|
784
785
|
}
|
|
785
786
|
const freshPayments = freshRecord.payments;
|
|
786
787
|
const mergedPayments = (0, _paymentUtils.mergeOrderPaymentListsByIdentity)(localPayments, freshPayments, {
|
|
787
|
-
preserveUnmatchedExistingWhen: _paymentUtils.isIdentifiedPendingOrderPayment
|
|
788
|
+
preserveUnmatchedExistingWhen: payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) || (0, _localEftposState.isLocalFinalizedPayment)(payment)
|
|
788
789
|
});
|
|
789
790
|
const remoteIdentityKeys = new Set(freshPayments.flatMap(payment => (0, _paymentUtils.getOrderPaymentIdentityKeys)(payment)));
|
|
790
791
|
const preservedPending = localPayments.filter(payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) && !(0, _paymentUtils.getOrderPaymentIdentityKeys)(payment).some(key => remoteIdentityKeys.has(key)));
|
|
@@ -1337,6 +1338,19 @@ class Server {
|
|
|
1337
1338
|
method: 'post',
|
|
1338
1339
|
path: '/shop/local/updateLocalPayment',
|
|
1339
1340
|
handler: this.handleUpdateLocalPayment.bind(this)
|
|
1341
|
+
}, {
|
|
1342
|
+
method: 'post',
|
|
1343
|
+
path: '/shop/local/finalizeLocalEftposPayment',
|
|
1344
|
+
handler: this.handleFinalizeLocalEftposPayment.bind(this)
|
|
1345
|
+
}, {
|
|
1346
|
+
method: 'post',
|
|
1347
|
+
path: '/shop/local/orderPaymentReadiness',
|
|
1348
|
+
handler: async ({
|
|
1349
|
+
data
|
|
1350
|
+
}) => ({
|
|
1351
|
+
code: 200,
|
|
1352
|
+
data: await this.order?.getLocalOrderByLookup(data?.sale_id)
|
|
1353
|
+
})
|
|
1340
1354
|
}, {
|
|
1341
1355
|
method: 'post',
|
|
1342
1356
|
path: '/shop/machinecode/batch',
|
|
@@ -2998,7 +3012,7 @@ class Server {
|
|
|
2998
3012
|
data: null
|
|
2999
3013
|
};
|
|
3000
3014
|
}
|
|
3001
|
-
const queueKey =
|
|
3015
|
+
const queueKey = String(saleId);
|
|
3002
3016
|
return this.runLocalPaymentUpdateInQueue(queueKey, () => this.processLocalPaymentUpdate({
|
|
3003
3017
|
operationId,
|
|
3004
3018
|
saleId,
|
|
@@ -3010,6 +3024,42 @@ class Server {
|
|
|
3010
3024
|
hasSurchargeInput
|
|
3011
3025
|
}), requestContext);
|
|
3012
3026
|
};
|
|
3027
|
+
handleFinalizeLocalEftposPayment = async ({
|
|
3028
|
+
data
|
|
3029
|
+
}) => {
|
|
3030
|
+
const {
|
|
3031
|
+
sale_id: saleId,
|
|
3032
|
+
payment_number: paymentNumber,
|
|
3033
|
+
result,
|
|
3034
|
+
failed_transaction_numbers: numbers = []
|
|
3035
|
+
} = data || {};
|
|
3036
|
+
if (!saleId || !paymentNumber || !Array.isArray(numbers) || numbers.some(number => typeof number !== 'string' || !number.trim()) || result !== 'failed' && result !== 'not_started' || result === 'failed' && numbers.length === 0 || result === 'not_started' && numbers.length !== 0) {
|
|
3037
|
+
return {
|
|
3038
|
+
code: 400,
|
|
3039
|
+
status: false,
|
|
3040
|
+
message: '缺少明确的支付结果或交易编号',
|
|
3041
|
+
data: null
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
return this.runLocalPaymentUpdateInQueue(String(saleId), async () => {
|
|
3045
|
+
if (!this.order) throw new Error('Order 模块未注册');
|
|
3046
|
+
const finalized = await this.order.finalizeLocalEftposPayment({
|
|
3047
|
+
saleId,
|
|
3048
|
+
paymentNumber,
|
|
3049
|
+
result,
|
|
3050
|
+
failedTransactionNumbers: [...new Set(numbers)]
|
|
3051
|
+
});
|
|
3052
|
+
return {
|
|
3053
|
+
code: 200,
|
|
3054
|
+
status: true,
|
|
3055
|
+
local_only: true,
|
|
3056
|
+
data: finalized
|
|
3057
|
+
};
|
|
3058
|
+
}, {
|
|
3059
|
+
sale_id: saleId,
|
|
3060
|
+
payment_number: paymentNumber
|
|
3061
|
+
});
|
|
3062
|
+
};
|
|
3013
3063
|
async runLocalPaymentUpdateInQueue(key, task, context) {
|
|
3014
3064
|
const queuedAt = Date.now();
|
|
3015
3065
|
const hasPreviousTask = this.localPaymentUpdateQueues.has(key);
|
|
@@ -3769,7 +3819,7 @@ class Server {
|
|
|
3769
3819
|
device_id: debugDeviceId,
|
|
3770
3820
|
payload: {
|
|
3771
3821
|
backendPath: params.backendPath,
|
|
3772
|
-
data: params.data,
|
|
3822
|
+
data: (0, _localEftposState.stripLocalEftposState)(params.data),
|
|
3773
3823
|
invoice: this.normalizePrintInvoiceLayoutConfig(params.invoiceLayoutConfig),
|
|
3774
3824
|
template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
|
|
3775
3825
|
external_sale_number: params.data?.external_sale_number,
|
|
@@ -4324,6 +4374,16 @@ class Server {
|
|
|
4324
4374
|
backendPath,
|
|
4325
4375
|
title
|
|
4326
4376
|
} = params;
|
|
4377
|
+
const lookup = params.data?.order_id || params.data?.external_sale_number;
|
|
4378
|
+
const localOrder = lookup && (await this.order?.getLocalOrderByLookup?.(lookup));
|
|
4379
|
+
if ((0, _localEftposState.requiresLocalPaymentRefresh)(params.data) || (0, _localEftposState.requiresLocalPaymentRefresh)(localOrder)) {
|
|
4380
|
+
return {
|
|
4381
|
+
code: 409,
|
|
4382
|
+
status: false,
|
|
4383
|
+
message: _localEftposState.PAYMENT_REFRESH_MESSAGE,
|
|
4384
|
+
data: null
|
|
4385
|
+
};
|
|
4386
|
+
}
|
|
4327
4387
|
const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
|
|
4328
4388
|
includePublicInvoice: true
|
|
4329
4389
|
});
|
|
@@ -5072,7 +5132,15 @@ class Server {
|
|
|
5072
5132
|
order: null
|
|
5073
5133
|
};
|
|
5074
5134
|
}
|
|
5075
|
-
await this.order.
|
|
5135
|
+
const localBeforeRefresh = this.order.getLocalOrderFromMemoryByLookup?.(lookup) || (this.shouldForceRemoteSalesDetail(data) ? await this.order.getLocalOrderByLookup(lookup) : null);
|
|
5136
|
+
if ((0, _localEftposState.requiresLocalPaymentRefresh)(localBeforeRefresh)) {
|
|
5137
|
+
if (String(fresh.order_id) !== String(localBeforeRefresh.order_id)) {
|
|
5138
|
+
throw new Error('远端订单身份不匹配,请重新刷新');
|
|
5139
|
+
}
|
|
5140
|
+
await this.order.completeLocalPaymentRefresh(fresh);
|
|
5141
|
+
} else {
|
|
5142
|
+
await this.order.upsertOrdersFromRemote([fresh]);
|
|
5143
|
+
}
|
|
5076
5144
|
let savedOrder = await this.order.getLocalOrderByLookup(lookup);
|
|
5077
5145
|
if (!savedOrder && fresh.order_id !== undefined && fresh.order_id !== null && String(fresh.order_id) !== String(lookup)) {
|
|
5078
5146
|
savedOrder = await this.order.getLocalOrderByLookup(fresh.order_id);
|
|
@@ -123,11 +123,24 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
123
123
|
paymentNumber: OrderId;
|
|
124
124
|
operationId?: string;
|
|
125
125
|
updates: Record<string, any>;
|
|
126
|
+
finalizeUnsuccessful?: boolean;
|
|
126
127
|
}): Promise<{
|
|
127
128
|
order: OrderData;
|
|
128
129
|
payment: Record<string, any>;
|
|
129
130
|
previousPayment: Record<string, any>;
|
|
130
131
|
} | null>;
|
|
132
|
+
finalizeLocalEftposPayment(params: {
|
|
133
|
+
saleId: OrderId;
|
|
134
|
+
paymentNumber: OrderId;
|
|
135
|
+
result: 'failed' | 'not_started';
|
|
136
|
+
failedTransactionNumbers: string[];
|
|
137
|
+
}): Promise<{
|
|
138
|
+
order: OrderData;
|
|
139
|
+
payment: Record<string, any>;
|
|
140
|
+
previousPayment: Record<string, any>;
|
|
141
|
+
} | null>;
|
|
142
|
+
/** Only a successful full detail GET may release the persisted payment gate. */
|
|
143
|
+
completeLocalPaymentRefresh(fresh: OrderData): Promise<OrderData>;
|
|
131
144
|
loadOrdersByServer(options?: {
|
|
132
145
|
trigger?: string;
|
|
133
146
|
}): Promise<OrderData[]>;
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.OrderModule = void 0;
|
|
7
7
|
var _lodashEs = require("lodash-es");
|
|
8
|
+
var _localEftposState = require("../../../modules/Order/localEftposState");
|
|
8
9
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
9
10
|
var _BaseModule = require("../../../modules/BaseModule");
|
|
10
11
|
var _orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
|
|
@@ -259,9 +260,9 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
259
260
|
mergeAuthoritativeOrderCollection(field, existing, incoming, uidRemaps = [], options = {}) {
|
|
260
261
|
const kind = ORDER_COLLECTION_KIND_BY_FIELD[field];
|
|
261
262
|
const existingItems = Array.isArray(existing) ? existing : [];
|
|
262
|
-
if (field === 'payments' && options.preserveIdentifiedPendingPayments) {
|
|
263
|
+
if (field === 'payments' && (options.preserveIdentifiedPendingPayments || existingItems.some(_localEftposState.isLocalFinalizedPayment))) {
|
|
263
264
|
return (0, _paymentUtils.mergeOrderPaymentListsByIdentity)(existingItems, incoming, {
|
|
264
|
-
preserveUnmatchedExistingWhen: _paymentUtils.isIdentifiedPendingOrderPayment
|
|
265
|
+
preserveUnmatchedExistingWhen: payment => options.preserveIdentifiedPendingPayments === true && (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) || (0, _localEftposState.isLocalFinalizedPayment)(payment)
|
|
265
266
|
});
|
|
266
267
|
}
|
|
267
268
|
return incoming.map(incomingItem => {
|
|
@@ -753,6 +754,13 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
753
754
|
return null;
|
|
754
755
|
}
|
|
755
756
|
const previousPayment = (0, _lodashEs.cloneDeep)(found.payment);
|
|
757
|
+
if (params.finalizeUnsuccessful && (found.payment.status === 'paid' || found.payment.metadata?.transactions?.some(transaction => transaction.status === 'success') || (0, _localEftposState.isLocalFinalizedPayment)(found.payment))) {
|
|
758
|
+
return {
|
|
759
|
+
order: found.order,
|
|
760
|
+
payment: found.payment,
|
|
761
|
+
previousPayment
|
|
762
|
+
};
|
|
763
|
+
}
|
|
756
764
|
const nextPayment = (0, _paymentUtils.mergeOrderPaymentRecord)(found.payment, {
|
|
757
765
|
...params.updates,
|
|
758
766
|
updated_at: params.updates.updated_at || (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss')
|
|
@@ -762,6 +770,13 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
762
770
|
payments: (found.order.payments || []).map((payment, index) => index === found.paymentIndex ? nextPayment : payment),
|
|
763
771
|
updated_at: (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss')
|
|
764
772
|
};
|
|
773
|
+
if (params.finalizeUnsuccessful && this.hasRealOrderId(found.order) && Number(found.order.need_sync || 0) === 0 && !(nextOrder.payments || []).some(_paymentUtils.isPendingOrderPayment)) {
|
|
774
|
+
nextOrder.is_draft_order = 0;
|
|
775
|
+
nextOrder.metadata = {
|
|
776
|
+
...nextOrder.metadata,
|
|
777
|
+
[_localEftposState.LOCAL_EFTPOS_REFRESH_REQUIRED]: true
|
|
778
|
+
};
|
|
779
|
+
}
|
|
765
780
|
const paymentContext = {
|
|
766
781
|
...logContext,
|
|
767
782
|
matched_by: found.matchedBy,
|
|
@@ -813,6 +828,60 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
813
828
|
previousPayment
|
|
814
829
|
};
|
|
815
830
|
}
|
|
831
|
+
async finalizeLocalEftposPayment(params) {
|
|
832
|
+
const found = await this.getLocalPayment(params);
|
|
833
|
+
if (!found) throw new Error('未找到本地支付记录');
|
|
834
|
+
const transactions = found.payment.metadata?.transactions || [];
|
|
835
|
+
if (transactions.some(transaction => transaction.status !== 'fail' && transaction.status !== 'success' && !params.failedTransactionNumbers.includes(String(transaction.number)))) {
|
|
836
|
+
throw new Error('仍有结果未知的交易,保留待确认支付');
|
|
837
|
+
}
|
|
838
|
+
if (params.result === 'not_started' && transactions.length > 0 && !(0, _localEftposState.isLocalFinalizedPayment)(found.payment) && found.payment.status !== 'paid') {
|
|
839
|
+
throw new Error('支付已有交易记录,不能按未发起交易结束');
|
|
840
|
+
}
|
|
841
|
+
return this.updateLocalPayment({
|
|
842
|
+
...params,
|
|
843
|
+
finalizeUnsuccessful: true,
|
|
844
|
+
updates: {
|
|
845
|
+
status: 'voided',
|
|
846
|
+
metadata: {
|
|
847
|
+
[_localEftposState.LOCAL_EFTPOS_FINALIZED]: true,
|
|
848
|
+
transactions: params.failedTransactionNumbers.map(number => ({
|
|
849
|
+
number,
|
|
850
|
+
status: 'fail'
|
|
851
|
+
}))
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** Only a successful full detail GET may release the persisted payment gate. */
|
|
858
|
+
async completeLocalPaymentRefresh(fresh) {
|
|
859
|
+
const existing = await this.getLocalOrderByLookup(fresh.order_id);
|
|
860
|
+
if (!existing || !(0, _localEftposState.requiresLocalPaymentRefresh)(existing)) {
|
|
861
|
+
await this.upsertOrdersFromRemote([fresh]);
|
|
862
|
+
return (await this.getLocalOrderByLookup(fresh.order_id)) || fresh;
|
|
863
|
+
}
|
|
864
|
+
if (this.isPendingSyncOrder(existing) || this.isDraftOrder(existing) || (existing.payments || []).some(_paymentUtils.isPendingOrderPayment)) {
|
|
865
|
+
throw new Error('订单仍有未结束支付或待同步改动,暂不能刷新');
|
|
866
|
+
}
|
|
867
|
+
if (!Array.isArray(fresh.products) || !fresh.summary) {
|
|
868
|
+
throw new Error('远端订单详情不完整,请重新刷新');
|
|
869
|
+
}
|
|
870
|
+
const nextOrder = this.mergeOrderRecords(existing, this.normalizeRemoteSyncedOrder(fresh));
|
|
871
|
+
nextOrder.metadata = {
|
|
872
|
+
...nextOrder.metadata
|
|
873
|
+
};
|
|
874
|
+
delete nextOrder.metadata[_localEftposState.LOCAL_EFTPOS_REFRESH_REQUIRED];
|
|
875
|
+
// Do not publish/clear the gate in memory until SQLite has accepted the full snapshot.
|
|
876
|
+
await this.updateOrderInSQLite(nextOrder, 'completeLocalPaymentRefresh', {
|
|
877
|
+
throwOnError: true
|
|
878
|
+
});
|
|
879
|
+
const index = this.findOrderIndexByIdentity(this.store.list, existing);
|
|
880
|
+
this.store.list = index < 0 ? [...this.store.list, nextOrder] : this.store.list.map((order, i) => i === index ? nextOrder : order);
|
|
881
|
+
this.syncOrdersMap();
|
|
882
|
+
await this.emitOrdersChanged([nextOrder], 'completeLocalPaymentRefresh');
|
|
883
|
+
return nextOrder;
|
|
884
|
+
}
|
|
816
885
|
async loadOrdersByServer(options = {}) {
|
|
817
886
|
let orderList = [];
|
|
818
887
|
let hasFetchedRemoteSnapshot = false;
|
|
@@ -1997,7 +2066,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
1997
2066
|
return payments.filter(payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment));
|
|
1998
2067
|
}
|
|
1999
2068
|
shouldProtectLocalOrderFromRemoteSnapshot(order) {
|
|
2000
|
-
return this.isPendingSyncOrder(order) || this.isDraftOrder(order) || this.getIdentifiedPendingPayments(order).length > 0;
|
|
2069
|
+
return this.isPendingSyncOrder(order) || this.isDraftOrder(order) || this.getIdentifiedPendingPayments(order).length > 0 || (0, _localEftposState.requiresLocalPaymentRefresh)(order) || (order.payments || []).some(_localEftposState.isLocalFinalizedPayment);
|
|
2001
2070
|
}
|
|
2002
2071
|
getUnmatchedIdentifiedPendingPayments(existing, incoming) {
|
|
2003
2072
|
const existingPayments = Array.isArray(existing) ? existing : [];
|
|
@@ -2019,6 +2088,12 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
2019
2088
|
const secondaryRecord = secondary;
|
|
2020
2089
|
const existingRecord = existing;
|
|
2021
2090
|
const incomingRecord = incoming;
|
|
2091
|
+
if ((0, _localEftposState.requiresLocalPaymentRefresh)(existing)) {
|
|
2092
|
+
mergedRecord.metadata = {
|
|
2093
|
+
...mergedRecord.metadata,
|
|
2094
|
+
[_localEftposState.LOCAL_EFTPOS_REFRESH_REQUIRED]: true
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2022
2097
|
const childUidRemaps = [];
|
|
2023
2098
|
for (const field of fieldsToPreserve) {
|
|
2024
2099
|
if (!this.isBlankIdentityValue(preferredRecord[field])) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Module, ModuleOptions, PisellCore } from '../../types';
|
|
2
2
|
import { BaseModule } from '../../modules/BaseModule';
|
|
3
|
+
import { type LocalEftposFinalization } from '../../modules/Order/localEftposState';
|
|
3
4
|
import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentStatus, BaseSalesSubmitSalesOrderParams, BaseSalesUpdateOrderProductQuantityParams, BaseSalesCalculateProductBookingPriceParams, BaseSalesPriceRecalculationContextChange, BaseSalesProductBookingPriceResult, BaseSalesQuotationCustomerScopeInfo, BaseSalesScanCodeResult, BaseSalesCashPaymentOptions, BaseSalesCashPaymentConfig, BaseSalesPayCashParams, BaseSalesPayCashResult, BaseSalesCommitPaymentMethodParams, BaseSalesCommitPaymentMethodResult, BaseSalesConfirmWalletPaymentResult } from './types';
|
|
4
5
|
import { OrderModule } from '../../modules/Order';
|
|
5
6
|
import { type PickupTimingBookingInput } from '../../modules/Order/pickupTiming';
|
|
@@ -105,6 +106,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
105
106
|
private isCurrentSalesOrderRecord;
|
|
106
107
|
setServerOrderChangeSyncEnabled(enabled: boolean): void;
|
|
107
108
|
private queueLatestServerOrderChange;
|
|
109
|
+
private shouldSyncServerOrderChanges;
|
|
108
110
|
private drainQueuedServerOrderChanges;
|
|
109
111
|
private syncCurrentSalesDetailFromServerOrderChange;
|
|
110
112
|
private registerServerOrderChangeSync;
|
|
@@ -353,6 +355,10 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
353
355
|
* payments 会交给 OrderModule 统一映射为 Sales 协议支付项。
|
|
354
356
|
*/
|
|
355
357
|
submitSalesOrder<T = any>(params?: BaseSalesSubmitSalesOrderParams): Promise<T>;
|
|
358
|
+
private pendingLocalEftposFinalizations;
|
|
359
|
+
finalizeLocalEftposPayment(params: LocalEftposFinalization): Promise<any>;
|
|
360
|
+
/** Entry gate also reads SQLite state, so stale sessions/restarts cannot bypass it. */
|
|
361
|
+
ensureSalesOrderReadyForPayment(): Promise<void>;
|
|
356
362
|
saveSalesOrderDraft<T = any>(params?: {
|
|
357
363
|
platform?: string;
|
|
358
364
|
businessCode?: string;
|
|
@@ -8,6 +8,7 @@ var _exportNames = {
|
|
|
8
8
|
};
|
|
9
9
|
exports.BaseSalesImpl = void 0;
|
|
10
10
|
var _BaseModule = require("../../modules/BaseModule");
|
|
11
|
+
var _localEftposState = require("../../modules/Order/localEftposState");
|
|
11
12
|
var _types = require("./types");
|
|
12
13
|
Object.keys(_types).forEach(function (key) {
|
|
13
14
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -590,8 +591,12 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
590
591
|
}
|
|
591
592
|
this.queuedServerOrderChanges.push(snapshot);
|
|
592
593
|
}
|
|
594
|
+
shouldSyncServerOrderChanges() {
|
|
595
|
+
const payments = this.store.order?.getTempOrder()?.payments || [];
|
|
596
|
+
return this.serverOrderChangeSyncEnabled || payments.some(_localEftposState.isLocalFinalizedPayment) && !payments.some(_paymentUtils.isPendingOrderPayment);
|
|
597
|
+
}
|
|
593
598
|
async drainQueuedServerOrderChanges() {
|
|
594
|
-
if (!this.
|
|
599
|
+
if (!this.shouldSyncServerOrderChanges() || this.salesDetailLoadInFlightCount > 0 || this.serverOrderChangeHydrateInFlight || this.queuedServerOrderChanges.length === 0) return;
|
|
595
600
|
const changedOrders = this.queuedServerOrderChanges;
|
|
596
601
|
this.queuedServerOrderChanges = [];
|
|
597
602
|
await this.syncCurrentSalesDetailFromServerOrderChange({
|
|
@@ -599,8 +604,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
599
604
|
});
|
|
600
605
|
}
|
|
601
606
|
async syncCurrentSalesDetailFromServerOrderChange(payload) {
|
|
602
|
-
const
|
|
603
|
-
|
|
607
|
+
const fullSnapshotAfterFinalization = Array.isArray(payload?.list) && this.store.order?.getTempOrder()?.payments?.some(_localEftposState.isLocalFinalizedPayment);
|
|
608
|
+
const changedOrders = Array.isArray(payload) ? payload : Array.isArray(payload?.changedOrders) && payload.changedOrders.length > 0 ? payload.changedOrders : fullSnapshotAfterFinalization ? payload.list : payload?.data && typeof payload.data === 'object' ? [payload.data] : [];
|
|
609
|
+
if (!this.shouldSyncServerOrderChanges()) return;
|
|
604
610
|
|
|
605
611
|
// 显式详情加载期间 current 可能仍是上一单,先按订单身份 latest-only 缓存;
|
|
606
612
|
// load 完成后再用新的 current 过滤,避免恰好到达的最新支付快照丢失。
|
|
@@ -1043,7 +1049,30 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
1043
1049
|
customerId: params.customerId,
|
|
1044
1050
|
channel: params.channel
|
|
1045
1051
|
});
|
|
1046
|
-
|
|
1052
|
+
const shouldAlignSelectedVariantPrice = handledByOsServer && this.preferAuthoritativeProductQueryPrice();
|
|
1053
|
+
return productIds.map((productId, index) => {
|
|
1054
|
+
const authoritativeProduct = productId === null ? null : productsById.get(productId) || null;
|
|
1055
|
+
if (!shouldAlignSelectedVariantPrice || !authoritativeProduct) return authoritativeProduct;
|
|
1056
|
+
const selectedProduct = selectedProducts[index];
|
|
1057
|
+
const selectedVariantId = this.getSelectedProductVariantId(selectedProduct);
|
|
1058
|
+
if (!selectedVariantId || this.getSelectedProductVariantId(authoritativeProduct) === selectedVariantId) {
|
|
1059
|
+
return authoritativeProduct;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// OS 返回的是目录商品;已命中的智能价格不能被规格基础价覆盖。
|
|
1063
|
+
const appliedFields = authoritativeProduct._dataVariant?.appliedFields;
|
|
1064
|
+
if (Array.isArray(appliedFields) && ['price', 'selling_price', 'original_price', 'base_price'].some(field => appliedFields.includes(field))) {
|
|
1065
|
+
return authoritativeProduct;
|
|
1066
|
+
}
|
|
1067
|
+
const selectedVariant = Array.isArray(authoritativeProduct.variant) ? authoritativeProduct.variant.find(variant => Number(variant?.id) === selectedVariantId) : undefined;
|
|
1068
|
+
const selectedPrice = [selectedVariant?.price, selectedVariant?.selling_price, selectedVariant?.base_price, params.priceQueries[index].fallback_price, selectedProduct.price, selectedProduct.selling_price, selectedProduct.original_price].find(value => (typeof value === 'number' || typeof value === 'string' && value.trim() !== '') && Number.isFinite(Number(value)));
|
|
1069
|
+
|
|
1070
|
+
// 每行独立保留所选规格价格(含 0 元),避免同商品多规格共用目录对象串价。
|
|
1071
|
+
return selectedPrice === undefined ? authoritativeProduct : {
|
|
1072
|
+
...authoritativeProduct,
|
|
1073
|
+
price: selectedPrice
|
|
1074
|
+
};
|
|
1075
|
+
});
|
|
1047
1076
|
}
|
|
1048
1077
|
const queryPayload = this.buildPriceQueryPayload({
|
|
1049
1078
|
ids,
|
|
@@ -2728,6 +2757,102 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
2728
2757
|
};
|
|
2729
2758
|
return this.shouldUseCheckoutSyncTask() ? this.store.order.submitTempOrder(submitParams) : this.store.order.submitTempOrderAsync(submitParams);
|
|
2730
2759
|
}
|
|
2760
|
+
pendingLocalEftposFinalizations = new Map();
|
|
2761
|
+
async finalizeLocalEftposPayment(params) {
|
|
2762
|
+
const key = `${params.saleId}:${params.paymentNumber}`;
|
|
2763
|
+
// If SQLite rejects the write, retry this known result before another payment can start.
|
|
2764
|
+
this.pendingLocalEftposFinalizations.set(key, params);
|
|
2765
|
+
const response = await this.request.post('/local/finalizeLocalEftposPayment', {
|
|
2766
|
+
sale_id: params.saleId,
|
|
2767
|
+
payment_number: params.paymentNumber,
|
|
2768
|
+
result: params.result,
|
|
2769
|
+
failed_transaction_numbers: params.failedTransactionNumbers
|
|
2770
|
+
}, {
|
|
2771
|
+
osServer: true,
|
|
2772
|
+
customToast: () => {}
|
|
2773
|
+
});
|
|
2774
|
+
if (response?.code !== 200 || !response?.data?.order) {
|
|
2775
|
+
throw new Error(response?.message || '本地支付收尾失败,请刷新订单后重试');
|
|
2776
|
+
}
|
|
2777
|
+
this.pendingLocalEftposFinalizations.delete(key);
|
|
2778
|
+
const {
|
|
2779
|
+
order,
|
|
2780
|
+
payment
|
|
2781
|
+
} = response.data;
|
|
2782
|
+
const temp = this.getTempOrder();
|
|
2783
|
+
const isCurrentOrder = String(temp?.external_sale_number || temp?.order_id) === String(params.saleId) || temp?.order_id && String(temp.order_id) === String(order.order_id);
|
|
2784
|
+
if (isCurrentOrder) {
|
|
2785
|
+
// Persisted by the local route already. Saving a draft here would re-enable protection.
|
|
2786
|
+
if (temp?.payments?.some(item => String(item.payment_number) === params.paymentNumber)) {
|
|
2787
|
+
await this.updateOrderPayment(params.paymentNumber, payment, {
|
|
2788
|
+
persistDraft: false,
|
|
2789
|
+
submitWhenPaid: false
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
if ((0, _localEftposState.requiresLocalPaymentRefresh)(order)) {
|
|
2793
|
+
// Set the in-memory gate before the GET so a failed refresh also blocks this page.
|
|
2794
|
+
const currentTemp = this.getTempOrder();
|
|
2795
|
+
if (currentTemp) currentTemp.metadata = {
|
|
2796
|
+
...currentTemp.metadata,
|
|
2797
|
+
...order.metadata
|
|
2798
|
+
};
|
|
2799
|
+
try {
|
|
2800
|
+
await this.loadSalesDetail({
|
|
2801
|
+
orderId: order.order_id,
|
|
2802
|
+
forceRemote: true,
|
|
2803
|
+
merge: false
|
|
2804
|
+
});
|
|
2805
|
+
} catch {
|
|
2806
|
+
throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
} else if ((0, _localEftposState.requiresLocalPaymentRefresh)(order)) {
|
|
2810
|
+
const refreshed = await this.store.order?.getSalesOrderByLookup({
|
|
2811
|
+
lookup: order.order_id,
|
|
2812
|
+
forceRemote: true
|
|
2813
|
+
});
|
|
2814
|
+
if (refreshed?.code !== 200) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
|
|
2815
|
+
}
|
|
2816
|
+
return response.data;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
/** Entry gate also reads SQLite state, so stale sessions/restarts cannot bypass it. */
|
|
2820
|
+
async ensureSalesOrderReadyForPayment() {
|
|
2821
|
+
const temp = this.getTempOrder();
|
|
2822
|
+
const unfinished = [...this.pendingLocalEftposFinalizations.values()].filter(params => String(params.saleId) === String(temp?.external_sale_number) || String(params.saleId) === String(temp?.order_id));
|
|
2823
|
+
if (unfinished.length > 0) {
|
|
2824
|
+
for (const params of unfinished) await this.finalizeLocalEftposPayment(params);
|
|
2825
|
+
throw new Error('订单已更新,请确认商品和金额后重新付款');
|
|
2826
|
+
}
|
|
2827
|
+
const lookup = temp?.order_id || temp?.external_sale_number;
|
|
2828
|
+
let localOrder = null;
|
|
2829
|
+
if (lookup && this.core?.server?.hasRoute?.('post', '/shop/local/orderPaymentReadiness')) {
|
|
2830
|
+
const response = await this.request.post('/local/orderPaymentReadiness', {
|
|
2831
|
+
sale_id: lookup
|
|
2832
|
+
}, {
|
|
2833
|
+
osServer: true,
|
|
2834
|
+
customToast: () => {}
|
|
2835
|
+
});
|
|
2836
|
+
if (response?.code !== 200) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
|
|
2837
|
+
localOrder = response.data;
|
|
2838
|
+
}
|
|
2839
|
+
if (!(0, _localEftposState.requiresLocalPaymentRefresh)(temp) && !(0, _localEftposState.requiresLocalPaymentRefresh)(localOrder)) return;
|
|
2840
|
+
const orderId = localOrder?.order_id || temp?.order_id;
|
|
2841
|
+
if (!orderId || String(orderId) === String(temp?.external_sale_number)) {
|
|
2842
|
+
throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
|
|
2843
|
+
}
|
|
2844
|
+
try {
|
|
2845
|
+
await this.loadSalesDetail({
|
|
2846
|
+
orderId,
|
|
2847
|
+
forceRemote: true,
|
|
2848
|
+
merge: false
|
|
2849
|
+
});
|
|
2850
|
+
} catch {
|
|
2851
|
+
throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
|
|
2852
|
+
}
|
|
2853
|
+
// The initiating UI may have captured the old amount before this await.
|
|
2854
|
+
throw new Error('订单已刷新,请确认商品和金额后重新付款');
|
|
2855
|
+
}
|
|
2731
2856
|
async saveSalesOrderDraft(params) {
|
|
2732
2857
|
if (!this.store.order) {
|
|
2733
2858
|
throw new Error('BaseSales 解决方案需要 order 模块支持');
|
|
@@ -328,7 +328,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
|
|
|
328
328
|
date: string;
|
|
329
329
|
status: string;
|
|
330
330
|
week: string;
|
|
331
|
-
weekNum: 0 |
|
|
331
|
+
weekNum: 0 | 5 | 3 | 1 | 2 | 4 | 6;
|
|
332
332
|
}[]>;
|
|
333
333
|
submitTimeSlot(timeSlots: TimeSliceItem): void;
|
|
334
334
|
private getScheduleDataByIds;
|