cyymall-cli 0.1.10 → 0.1.11

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.
@@ -1,375 +1,618 @@
1
- "use strict";
2
-
3
- const fs = require("fs");
4
- const crypto = require("crypto");
5
- const http = require("../http");
6
- const config = require("../config");
7
- const biz = require("../biz");
8
-
9
- function envelope(success, message, data, exitCode) {
10
- const traceId = `local-${crypto.randomBytes(8).toString("hex")}`;
11
- console.log(
12
- JSON.stringify(
13
- {
14
- success,
15
- code: success ? "OK" : "UPSTREAM_ERROR",
16
- message,
17
- data,
18
- traceId,
19
- },
20
- null,
21
- 2,
22
- ),
23
- );
24
- process.exit(exitCode ?? (success ? 0 : 2));
25
- }
26
-
27
- /**
28
- * @param {{ bodyFile?: string, bodyJson?: string }} opts
29
- */
30
- async function preSettle(opts) {
31
- const cfg = config.requireAuthSession();
32
- let raw = opts.bodyJson;
33
- if (opts.bodyFile) raw = fs.readFileSync(opts.bodyFile, "utf8");
34
- if (!raw) {
35
- console.error("cyy: provide --body-file or --body-json");
36
- process.exit(1);
37
- }
38
- const url = http.moduleUrl("ORDER", "/app/order/preSettleOrder");
39
- const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
40
- const { ok, json } = await http.request(url, {
41
- method: "POST",
42
- headers,
43
- body: raw,
44
- });
45
- const success = ok && biz.isBizSuccess(json);
46
- envelope(success, success ? "success" : "pre-settle failed", { upstream: json }, success ? 0 : 2);
47
- }
48
-
49
- /**
50
- * @param {{ bodyFile?: string, bodyJson?: string }} opts
51
- */
52
- async function confirm(opts) {
53
- const cfg = config.requireAuthSession();
54
- let raw = opts.bodyJson;
55
- if (opts.bodyFile) raw = fs.readFileSync(opts.bodyFile, "utf8");
56
- if (!raw) {
57
- console.error("cyy: provide --body-file or --body-json");
58
- process.exit(1);
59
- }
60
- const url = http.moduleUrl("ORDER", "/app/order/confirmOrder");
61
- const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
62
- const { ok, json } = await http.request(url, {
63
- method: "POST",
64
- headers,
65
- body: raw,
66
- });
67
- const success = ok && biz.isBizSuccess(json);
68
- envelope(success, success ? "success" : "confirm failed", { upstream: json }, success ? 0 : 2);
69
- }
70
-
71
- async function payUrl(opts) {
72
- const cfg = config.requireAuthSession();
73
- const orderId = opts.orderId;
74
- if (!orderId) {
75
- console.error("cyy: --order-id required");
76
- process.exit(1);
77
- }
78
-
79
- const url = http.moduleUrl("DEFAULT", "/member/getInfo/V2");
80
- const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
81
- delete headers["Content-Type"];
82
-
83
- const { ok, json } = await http.request(url, { method: "GET", headers, body: null });
84
-
85
- let phone = String(cfg.phone || "");
86
- let nick = "";
87
- let img = "";
88
- if (ok && biz.isBizSuccess(json) && json && typeof json === "object") {
89
- const d = /** @type {{data?:Record<string,unknown>}} */ (json).data;
90
- if (d) {
91
- phone = String(d.memberPhone || phone);
92
- nick = String(d.memberNick || "");
93
- img = String(d.headerImg || "");
94
- }
95
- }
96
-
97
- const siteId = cfg.site_id;
98
- const shopId = cfg.shop_id;
99
- const enc = encodeURIComponent;
100
- const payUrlStr = `https://dhcmall.ifoodbuy.com/H5/#/replacePay?phone=${enc(phone)}&orderid=${enc(String(orderId))}&img=${enc(img)}&nick=${enc(nick)}&siteId=${siteId}&shopId=${shopId}&share=true`;
101
-
102
- envelope(true, "success", { payUrl: payUrlStr, orderId }, 0);
103
- }
104
-
105
- /**
106
- * @param {object} opts
107
- */
108
- async function quick(opts) {
109
- const cfg = config.requireAuthSession();
110
-
111
- const keyword = opts.keyword;
112
- const quantity = Number(opts.quantity || 1);
113
- const unit = opts.unit || "袋";
114
- const shopId = Number(opts.shopId ?? cfg.shop_id);
115
- const siteId = Number(opts.siteId ?? cfg.site_id);
116
-
117
- const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
118
-
119
- // 1) Search
120
- const searchUrl = http.moduleUrl("PRODUCT", "/app/product/getSkuList");
121
- const searchBody = JSON.stringify({
122
- pageNum: 1,
123
- pageSize: 10,
124
- shopId,
125
- siteId,
126
- spuName: keyword,
127
- stockFlag: "0",
128
- });
129
- let r = await http.request(searchUrl, {
130
- method: "POST",
131
- headers,
132
- body: searchBody,
133
- });
134
- if (!r.ok || !biz.isBizSuccess(r.json)) {
135
- envelope(false, "search failed", { upstream: r.json }, 2);
136
- return;
137
- }
138
- const list =
139
- r.json &&
140
- typeof r.json === "object" &&
141
- r.json.data &&
142
- typeof r.json.data === "object"
143
- ? /** @type {{list?:unknown[]}} */ (r.json.data).list
144
- : [];
145
- const products = Array.isArray(list) ? list : [];
146
- if (!products.length) {
147
- envelope(false, "no products found", {}, 2);
148
- return;
149
- }
150
-
151
- const product = /** @type {Record<string,unknown>} */ (products[0]);
152
- const skuId = product.id;
153
- const skuCode = product.skuCode;
154
- const skuName = product.spuName;
155
- const stockQty = product.stockAllQuantity ?? 0;
156
- const promotionList = Array.isArray(product.promotionList) ? product.promotionList : [];
157
-
158
- let price = 0;
159
- let unitRate = 1;
160
- for (const p of promotionList) {
161
- const row = /** @type {Record<string,unknown>} */ (p);
162
- if (row.skuSaleUnitName === unit) {
163
- price = Number(row.salePrice || 0);
164
- unitRate = Number(row.unitRate ?? 1);
165
- break;
166
- }
167
- }
168
- if (!price) {
169
- envelope(
170
- false,
171
- `unit "${unit}" not found in promotionList`,
172
- { availableUnits: promotionList.map((x) => /** @type {{skuSaleUnitName?:string}} */ (x).skuSaleUnitName) },
173
- 2,
174
- );
175
- return;
176
- }
177
-
178
- // 2) Cart
179
- const cartBody = JSON.stringify({
180
- cartItemList: [
181
- {
182
- skuCode,
183
- skuName,
184
- skuQty: quantity,
185
- skuSaleUnit: unit,
186
- listPrice: price,
187
- salePrice: price,
188
- unitRate,
189
- selectFlag: true,
190
- },
191
- ],
192
- cartModel: "append",
193
- shopId,
194
- siteId,
195
- });
196
- const cartUrl = http.moduleUrl("ORDER", "/app/order/cart");
197
- r = await http.request(cartUrl, { method: "POST", headers, body: cartBody });
198
- if (!r.ok || !biz.isBizSuccess(r.json)) {
199
- envelope(false, "cart failed", { upstream: r.json }, 2);
200
- return;
201
- }
202
- const cartData =
203
- r.json && typeof r.json === "object" && "data" in r.json
204
- ? /** @type {{data?:{cartItemList?:{addTime?:string}[]}}} */ (r.json).data
205
- : undefined;
206
- const addTime = cartData?.cartItemList?.[0]?.addTime;
207
- if (!addTime) {
208
- envelope(false, "missing addTime from cart response", { upstream: r.json }, 2);
209
- return;
210
- }
211
-
212
- // 3) Pre-settle
213
- const preBody = JSON.stringify({
214
- headerVO: { shopId, siteId },
215
- itemVOList: [
216
- {
217
- skuId,
218
- skuCode,
219
- skuName,
220
- skuQty: quantity,
221
- skuSaleUnit: unit,
222
- salePrice: price,
223
- listPrice: price,
224
- unitRate,
225
- addTime,
226
- stockQty,
227
- minSaleUnit: unit,
228
- selectFlag: true,
229
- status: 0,
230
- canBuyFlag: true,
231
- },
232
- ],
233
- balanceFlag: false,
234
- userPointFlag: false,
235
- });
236
- const preUrl = http.moduleUrl("ORDER", "/app/order/preSettleOrder");
237
- r = await http.request(preUrl, { method: "POST", headers, body: preBody });
238
- if (!r.ok || !biz.isBizSuccess(r.json)) {
239
- envelope(false, "pre-settle failed", { upstream: r.json }, 2);
240
- return;
241
- }
242
- const pdata =
243
- r.json && typeof r.json === "object" && r.json.data && typeof r.json.data === "object"
244
- ? r.json.data
245
- : null;
246
- const preSettleId = pdata && /** @type {{preSettleId?:string}} */ (pdata).preSettleId;
247
- const headerVO = pdata && /** @type {{headerVO?:unknown}} */ (pdata).headerVO;
248
- const itemVOList = pdata && /** @type {{itemVOList?:unknown}} */ (pdata).itemVOList;
249
- if (!preSettleId) {
250
- envelope(false, "missing preSettleId", { upstream: r.json }, 2);
251
- return;
252
- }
253
-
254
- // 4) Confirm
255
- const confirmBody = JSON.stringify({
256
- preSettleId,
257
- headerVO,
258
- itemVOList,
259
- balanceFlag: false,
260
- userPointFlag: false,
261
- validateStockFlag: true,
262
- repeatFlag: false,
263
- });
264
- const confirmUrl = http.moduleUrl("ORDER", "/app/order/confirmOrder");
265
- r = await http.request(confirmUrl, { method: "POST", headers, body: confirmBody });
266
- if (!r.ok || !biz.isBizSuccess(r.json)) {
267
- envelope(false, "confirm failed", { upstream: r.json }, 2);
268
- return;
269
- }
270
- const orderData =
271
- r.json && typeof r.json === "object" && r.json.data && typeof r.json.data === "object"
272
- ? r.json.data
273
- : {};
274
- const orderId = /** @type {{orderId?:string,payAmt?:unknown}} */ (orderData).orderId;
275
- const payAmt = /** @type {{orderId?:string,payAmt?:unknown}} */ (orderData).payAmt;
276
-
277
- // 5) Pay URL
278
- const memberUrl = http.moduleUrl("DEFAULT", "/member/getInfo/V2");
279
- const h2 = /** @type {Record<string,string>} */ ({ ...headers });
280
- delete h2["Content-Type"];
281
- const uinfo = await http.request(memberUrl, { method: "GET", headers: h2, body: null });
282
- let phone = String(cfg.phone || "");
283
- let nick = "";
284
- let img = "";
285
- if (uinfo.ok && biz.isBizSuccess(uinfo.json) && uinfo.json && typeof uinfo.json === "object") {
286
- const d = /** @type {{data?:Record<string,unknown>}} */ (uinfo.json).data;
287
- if (d) {
288
- phone = String(d.memberPhone || phone);
289
- nick = String(d.memberNick || "");
290
- img = String(d.headerImg || "");
291
- }
292
- }
293
- const enc = encodeURIComponent;
294
- const payUrlStr = `https://dhcmall.ifoodbuy.com/H5/#/replacePay?phone=${enc(phone)}&orderid=${enc(String(orderId))}&img=${enc(img)}&nick=${enc(nick)}&siteId=${siteId}&shopId=${shopId}&share=true`;
295
-
296
- envelope(
297
- true,
298
- "order placed",
299
- {
300
- product: skuName,
301
- quantity,
302
- unit,
303
- orderId,
304
- payAmt,
305
- payUrl: payUrlStr,
306
- },
307
- 0,
308
- );
309
- }
310
-
311
- /**
312
- * 订单分页列表(与 App `OrderQeq` / `NetConfig.QUERY_PAGE` 一致)。
313
- * @param {{ page?: string, pageSize?: string, status?: string, shopId?: string, objectCode?: string, all?: boolean, shopKeyword?: string }} opts
314
- */
315
- async function list(opts) {
316
- const cfg = config.requireAuthSession();
317
- const shopId = opts.shopId != null && opts.shopId !== "" ? Number(opts.shopId) : Number(cfg.shop_id);
318
- if (!Number.isFinite(shopId)) {
319
- console.error("cyy: shopId missing (set shop_id in config or pass --shop-id)");
320
- process.exit(1);
321
- }
322
- const pageNum = Math.max(1, parseInt(String(opts.page ?? "1"), 10) || 1);
323
- const pageSize = Math.max(1, Math.min(100, parseInt(String(opts.pageSize ?? "10"), 10) || 10));
324
- const objectCode = parseInt(String(opts.objectCode ?? "1"), 10) || 1;
325
- const status = opts.status != null ? String(opts.status) : "";
326
- const queryAllFlag = Boolean(opts.all);
327
- const shopKeyWord = opts.shopKeyword != null ? String(opts.shopKeyword) : "";
328
-
329
- const bodyObj = {
330
- pageNum,
331
- pageSize,
332
- shopId,
333
- status,
334
- queryAllFlag,
335
- objectCode,
336
- shopKeyWord,
337
- };
338
- const url = http.moduleUrl("DEFAULT", "/order/queryPage");
339
- const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
340
- const { ok, json } = await http.request(url, {
341
- method: "POST",
342
- headers,
343
- body: JSON.stringify(bodyObj),
344
- });
345
- const success = ok && biz.isBizSuccess(json);
346
- envelope(success, success ? "success" : "order list failed", { upstream: json }, success ? 0 : 2);
347
- }
348
-
349
- /**
350
- * 取消订单(与 App `OrderReq` / `cancelOrder` 一致)。
351
- * @param {{ orderId?: string, childId?: string }} opts
352
- */
353
- async function cancel(opts) {
354
- const cfg = config.requireAuthSession();
355
- const orderId = opts.orderId;
356
- if (!orderId) {
357
- console.error("cyy: --order-id required");
358
- process.exit(1);
359
- }
360
- const bodyObj = /** @type {Record<string, unknown>} */ ({ orderId: String(orderId) });
361
- if (opts.childId != null && String(opts.childId) !== "") {
362
- bodyObj.childId = String(opts.childId);
363
- }
364
- const url = http.moduleUrl("ORDER", "/app/order/cancelOrder");
365
- const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
366
- const { ok, json } = await http.request(url, {
367
- method: "POST",
368
- headers,
369
- body: JSON.stringify(bodyObj),
370
- });
371
- const success = ok && biz.isBizSuccess(json);
372
- envelope(success, success ? "success" : "cancel order failed", { upstream: json }, success ? 0 : 2);
373
- }
374
-
375
- module.exports = { preSettle, confirm, payUrl, quick, list, cancel };
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const crypto = require("crypto");
5
+ const http = require("../http");
6
+ const config = require("../config");
7
+ const biz = require("../biz");
8
+ const { normalizeAddCartBody } = require("./cart");
9
+ const { resolveProductLine } = require("../cartProduct");
10
+
11
+ function envelope(success, message, data, exitCode) {
12
+ const traceId = `local-${crypto.randomBytes(8).toString("hex")}`;
13
+ console.log(
14
+ JSON.stringify(
15
+ {
16
+ success,
17
+ code: success ? "OK" : "UPSTREAM_ERROR",
18
+ message,
19
+ data,
20
+ traceId,
21
+ },
22
+ null,
23
+ 2,
24
+ ),
25
+ );
26
+ process.exit(exitCode ?? (success ? 0 : 2));
27
+ }
28
+
29
+ /**
30
+ * @param {{ bodyFile?: string, bodyJson?: string }} opts
31
+ */
32
+ async function preSettle(opts) {
33
+ const cfg = config.requireAuthSession();
34
+ let raw = opts.bodyJson;
35
+ if (opts.bodyFile) raw = fs.readFileSync(opts.bodyFile, "utf8");
36
+ if (!raw) {
37
+ console.error("cyy: provide --body-file or --body-json");
38
+ process.exit(1);
39
+ }
40
+ const url = http.moduleUrl("ORDER", "/app/order/preSettleOrder");
41
+ const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
42
+ const { ok, json } = await http.request(url, {
43
+ method: "POST",
44
+ headers,
45
+ body: raw,
46
+ });
47
+ const success = ok && biz.isBizSuccess(json);
48
+ envelope(success, success ? "success" : "pre-settle failed", { upstream: json }, success ? 0 : 2);
49
+ }
50
+
51
+ /**
52
+ * @param {{ bodyFile?: string, bodyJson?: string }} opts
53
+ */
54
+ async function confirm(opts) {
55
+ const cfg = config.requireAuthSession();
56
+ let raw = opts.bodyJson;
57
+ if (opts.bodyFile) raw = fs.readFileSync(opts.bodyFile, "utf8");
58
+ if (!raw) {
59
+ console.error("cyy: provide --body-file or --body-json");
60
+ process.exit(1);
61
+ }
62
+ const url = http.moduleUrl("ORDER", "/app/order/confirmOrder");
63
+ const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
64
+ const { ok, json } = await http.request(url, {
65
+ method: "POST",
66
+ headers,
67
+ body: raw,
68
+ });
69
+ const success = ok && biz.isBizSuccess(json);
70
+ envelope(success, success ? "success" : "confirm failed", { upstream: json }, success ? 0 : 2);
71
+ }
72
+
73
+ async function payUrl(opts) {
74
+ const cfg = config.requireAuthSession();
75
+ const orderId = opts.orderId;
76
+ if (!orderId) {
77
+ console.error("cyy: --order-id required");
78
+ process.exit(1);
79
+ }
80
+
81
+ const url = http.moduleUrl("DEFAULT", "/member/getInfo/V2");
82
+ const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
83
+ delete headers["Content-Type"];
84
+
85
+ const { ok, json } = await http.request(url, { method: "GET", headers, body: null });
86
+
87
+ let phone = String(cfg.phone || "");
88
+ let nick = "";
89
+ let img = "";
90
+ if (ok && biz.isBizSuccess(json) && json && typeof json === "object") {
91
+ const d = /** @type {{data?:Record<string,unknown>}} */ (json).data;
92
+ if (d) {
93
+ phone = String(d.memberPhone || phone);
94
+ nick = String(d.memberNick || "");
95
+ img = String(d.headerImg || "");
96
+ }
97
+ }
98
+
99
+ const siteId = cfg.site_id;
100
+ const shopId = cfg.shop_id;
101
+ const enc = encodeURIComponent;
102
+ const payUrlStr = `https://dhcmall.ifoodbuy.com/H5/#/replacePay?phone=${enc(phone)}&orderid=${enc(String(orderId))}&img=${enc(img)}&nick=${enc(nick)}&siteId=${siteId}&shopId=${shopId}&share=true`;
103
+
104
+ envelope(true, "success", { payUrl: payUrlStr, orderId }, 0);
105
+ }
106
+
107
+ /**
108
+ * @param {object} opts
109
+ */
110
+ async function quick(opts) {
111
+ const cfg = config.requireAuthSession();
112
+
113
+ const keyword = opts.keyword;
114
+ const quantity = Number(opts.quantity || 1);
115
+ const unit = opts.unit || "袋";
116
+ const shopId = Number(opts.shopId ?? cfg.shop_id);
117
+ const siteId = Number(opts.siteId ?? cfg.site_id);
118
+
119
+ const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
120
+
121
+ // 1) Search
122
+ const searchUrl = http.moduleUrl("PRODUCT", "/app/product/getSkuList");
123
+ const searchBody = JSON.stringify({
124
+ pageNum: 1,
125
+ pageSize: 10,
126
+ shopId,
127
+ siteId,
128
+ spuName: keyword,
129
+ stockFlag: "0",
130
+ });
131
+ let r = await http.request(searchUrl, {
132
+ method: "POST",
133
+ headers,
134
+ body: searchBody,
135
+ });
136
+ if (!r.ok || !biz.isBizSuccess(r.json)) {
137
+ envelope(false, "search failed", { upstream: r.json }, 2);
138
+ return;
139
+ }
140
+ const list =
141
+ r.json &&
142
+ typeof r.json === "object" &&
143
+ r.json.data &&
144
+ typeof r.json.data === "object"
145
+ ? /** @type {{list?:unknown[]}} */ (r.json.data).list
146
+ : [];
147
+ const products = Array.isArray(list) ? list : [];
148
+ if (!products.length) {
149
+ envelope(false, "no products found", {}, 2);
150
+ return;
151
+ }
152
+
153
+ const product = /** @type {Record<string,unknown>} */ (products[0]);
154
+ const skuId = product.id;
155
+ const skuCode = product.skuCode;
156
+ const skuName = product.spuName;
157
+ const stockQty = product.stockAllQuantity ?? 0;
158
+ const promotionList = Array.isArray(product.promotionList) ? product.promotionList : [];
159
+
160
+ let price = 0;
161
+ let unitRate = 1;
162
+ for (const p of promotionList) {
163
+ const row = /** @type {Record<string,unknown>} */ (p);
164
+ if (row.skuSaleUnitName === unit) {
165
+ price = Number(row.salePrice || 0);
166
+ unitRate = Number(row.unitRate ?? 1);
167
+ break;
168
+ }
169
+ }
170
+ if (!price) {
171
+ envelope(
172
+ false,
173
+ `unit "${unit}" not found in promotionList`,
174
+ { availableUnits: promotionList.map((x) => /** @type {{skuSaleUnitName?:string}} */ (x).skuSaleUnitName) },
175
+ 2,
176
+ );
177
+ return;
178
+ }
179
+
180
+ // 2) Cart
181
+ const cartBody = JSON.stringify({
182
+ cartItemList: [
183
+ {
184
+ skuCode,
185
+ skuName,
186
+ skuQty: quantity,
187
+ skuSaleUnit: unit,
188
+ listPrice: price,
189
+ salePrice: price,
190
+ unitRate,
191
+ selectFlag: true,
192
+ },
193
+ ],
194
+ cartModel: "append",
195
+ shopId,
196
+ siteId,
197
+ });
198
+ const cartUrl = http.moduleUrl("ORDER", "/app/order/cart");
199
+ r = await http.request(cartUrl, { method: "POST", headers, body: cartBody });
200
+ if (!r.ok || !biz.isBizSuccess(r.json)) {
201
+ envelope(false, "cart failed", { upstream: r.json }, 2);
202
+ return;
203
+ }
204
+ const cartData =
205
+ r.json && typeof r.json === "object" && "data" in r.json
206
+ ? /** @type {{data?:{cartItemList?:{addTime?:string}[]}}} */ (r.json).data
207
+ : undefined;
208
+ const addTime = cartData?.cartItemList?.[0]?.addTime;
209
+ if (!addTime) {
210
+ envelope(false, "missing addTime from cart response", { upstream: r.json }, 2);
211
+ return;
212
+ }
213
+
214
+ // 3) Pre-settle
215
+ const preBody = JSON.stringify({
216
+ headerVO: { shopId, siteId },
217
+ itemVOList: [
218
+ {
219
+ skuId,
220
+ skuCode,
221
+ skuName,
222
+ skuQty: quantity,
223
+ skuSaleUnit: unit,
224
+ salePrice: price,
225
+ listPrice: price,
226
+ unitRate,
227
+ addTime,
228
+ stockQty,
229
+ minSaleUnit: unit,
230
+ selectFlag: true,
231
+ status: 0,
232
+ canBuyFlag: true,
233
+ },
234
+ ],
235
+ balanceFlag: false,
236
+ userPointFlag: false,
237
+ });
238
+ const preUrl = http.moduleUrl("ORDER", "/app/order/preSettleOrder");
239
+ r = await http.request(preUrl, { method: "POST", headers, body: preBody });
240
+ if (!r.ok || !biz.isBizSuccess(r.json)) {
241
+ envelope(false, "pre-settle failed", { upstream: r.json }, 2);
242
+ return;
243
+ }
244
+ const pdata =
245
+ r.json && typeof r.json === "object" && r.json.data && typeof r.json.data === "object"
246
+ ? r.json.data
247
+ : null;
248
+ const preSettleId = pdata && /** @type {{preSettleId?:string}} */ (pdata).preSettleId;
249
+ const headerVO = pdata && /** @type {{headerVO?:unknown}} */ (pdata).headerVO;
250
+ const itemVOList = pdata && /** @type {{itemVOList?:unknown}} */ (pdata).itemVOList;
251
+ if (!preSettleId) {
252
+ envelope(false, "missing preSettleId", { upstream: r.json }, 2);
253
+ return;
254
+ }
255
+
256
+ // 4) Confirm
257
+ const confirmBody = JSON.stringify({
258
+ preSettleId,
259
+ headerVO,
260
+ itemVOList,
261
+ balanceFlag: false,
262
+ userPointFlag: false,
263
+ validateStockFlag: true,
264
+ repeatFlag: false,
265
+ });
266
+ const confirmUrl = http.moduleUrl("ORDER", "/app/order/confirmOrder");
267
+ r = await http.request(confirmUrl, { method: "POST", headers, body: confirmBody });
268
+ if (!r.ok || !biz.isBizSuccess(r.json)) {
269
+ envelope(false, "confirm failed", { upstream: r.json }, 2);
270
+ return;
271
+ }
272
+ const orderData =
273
+ r.json && typeof r.json === "object" && r.json.data && typeof r.json.data === "object"
274
+ ? r.json.data
275
+ : {};
276
+ const orderId = /** @type {{orderId?:string,payAmt?:unknown}} */ (orderData).orderId;
277
+ const payAmt = /** @type {{orderId?:string,payAmt?:unknown}} */ (orderData).payAmt;
278
+
279
+ // 5) Pay URL
280
+ const memberUrl = http.moduleUrl("DEFAULT", "/member/getInfo/V2");
281
+ const h2 = /** @type {Record<string,string>} */ ({ ...headers });
282
+ delete h2["Content-Type"];
283
+ const uinfo = await http.request(memberUrl, { method: "GET", headers: h2, body: null });
284
+ let phone = String(cfg.phone || "");
285
+ let nick = "";
286
+ let img = "";
287
+ if (uinfo.ok && biz.isBizSuccess(uinfo.json) && uinfo.json && typeof uinfo.json === "object") {
288
+ const d = /** @type {{data?:Record<string,unknown>}} */ (uinfo.json).data;
289
+ if (d) {
290
+ phone = String(d.memberPhone || phone);
291
+ nick = String(d.memberNick || "");
292
+ img = String(d.headerImg || "");
293
+ }
294
+ }
295
+ const enc = encodeURIComponent;
296
+ const payUrlStr = `https://dhcmall.ifoodbuy.com/H5/#/replacePay?phone=${enc(phone)}&orderid=${enc(String(orderId))}&img=${enc(img)}&nick=${enc(nick)}&siteId=${siteId}&shopId=${shopId}&share=true`;
297
+
298
+ envelope(
299
+ true,
300
+ "order placed",
301
+ {
302
+ product: skuName,
303
+ quantity,
304
+ unit,
305
+ orderId,
306
+ payAmt,
307
+ payUrl: payUrlStr,
308
+ },
309
+ 0,
310
+ );
311
+ }
312
+
313
+ /**
314
+ * 订单分页列表(与 App `OrderQeq` / `NetConfig.QUERY_PAGE` 一致)。
315
+ * @param {{ page?: string, pageSize?: string, status?: string, shopId?: string, objectCode?: string, all?: boolean, shopKeyword?: string }} opts
316
+ */
317
+ async function list(opts) {
318
+ const cfg = config.requireAuthSession();
319
+ const shopId = opts.shopId != null && opts.shopId !== "" ? Number(opts.shopId) : Number(cfg.shop_id);
320
+ if (!Number.isFinite(shopId)) {
321
+ console.error("cyy: shopId missing (set shop_id in config or pass --shop-id)");
322
+ process.exit(1);
323
+ }
324
+ const pageNum = Math.max(1, parseInt(String(opts.page ?? "1"), 10) || 1);
325
+ const pageSize = Math.max(1, Math.min(100, parseInt(String(opts.pageSize ?? "10"), 10) || 10));
326
+ const objectCode = parseInt(String(opts.objectCode ?? "1"), 10) || 1;
327
+ const status = opts.status != null ? String(opts.status) : "";
328
+ const queryAllFlag = Boolean(opts.all);
329
+ const shopKeyWord = opts.shopKeyword != null ? String(opts.shopKeyword) : "";
330
+
331
+ const bodyObj = {
332
+ pageNum,
333
+ pageSize,
334
+ shopId,
335
+ status,
336
+ queryAllFlag,
337
+ objectCode,
338
+ shopKeyWord,
339
+ };
340
+ const url = http.moduleUrl("DEFAULT", "/order/queryPage");
341
+ const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
342
+ const { ok, json } = await http.request(url, {
343
+ method: "POST",
344
+ headers,
345
+ body: JSON.stringify(bodyObj),
346
+ });
347
+ const success = ok && biz.isBizSuccess(json);
348
+ envelope(success, success ? "success" : "order list failed", { upstream: json }, success ? 0 : 2);
349
+ }
350
+
351
+ /**
352
+ * 取消订单(与 App `OrderReq` / `cancelOrder` 一致)。
353
+ * @param {{ orderId?: string, childId?: string }} opts
354
+ */
355
+ async function cancel(opts) {
356
+ const cfg = config.requireAuthSession();
357
+ const orderId = opts.orderId;
358
+ if (!orderId) {
359
+ console.error("cyy: --order-id required");
360
+ process.exit(1);
361
+ }
362
+ const bodyObj = /** @type {Record<string, unknown>} */ ({ orderId: String(orderId) });
363
+ if (opts.childId != null && String(opts.childId) !== "") {
364
+ bodyObj.childId = String(opts.childId);
365
+ }
366
+ const url = http.moduleUrl("ORDER", "/app/order/cancelOrder");
367
+ const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
368
+ const { ok, json } = await http.request(url, {
369
+ method: "POST",
370
+ headers,
371
+ body: JSON.stringify(bodyObj),
372
+ });
373
+ const success = ok && biz.isBizSuccess(json);
374
+ envelope(success, success ? "success" : "cancel order failed", { upstream: json }, success ? 0 : 2);
375
+ }
376
+
377
+ /**
378
+ * Multi-SKU checkout: search each keyword → one cart_add → pre_settle (→ optional confirm).
379
+ * @param {{ keywords?: string[], itemsFile?: string, itemsJson?: string, quantity?: string, unit?: string, shopId?: string, siteId?: string, cartModel?: string, preSettleOnly?: boolean }} opts
380
+ */
381
+ async function checkout(opts) {
382
+ const cfg = config.requireAuthSession();
383
+ const shopId = Number(opts.shopId ?? cfg.shop_id);
384
+ const siteId = Number(opts.siteId ?? cfg.site_id);
385
+ const quantity = Number(opts.quantity || 2);
386
+ const unit = opts.unit || "袋";
387
+ const cartModel = opts.cartModel || "append";
388
+ const headers = /** @type {Record<string,string>} */ (http.buildAuthHeaders(cfg));
389
+
390
+ /** @type {{ keyword: string, quantity?: number, unit?: string }[]} */
391
+ let itemSpecs = [];
392
+ if (opts.itemsFile) {
393
+ itemSpecs = JSON.parse(fs.readFileSync(opts.itemsFile, "utf8"));
394
+ } else if (opts.itemsJson) {
395
+ itemSpecs = JSON.parse(opts.itemsJson);
396
+ } else if (opts.keywords && opts.keywords.length) {
397
+ itemSpecs = opts.keywords.map((k) => ({ keyword: k, quantity, unit }));
398
+ }
399
+ if (!itemSpecs.length) {
400
+ console.error("cyy: provide --keyword (repeatable), --items-json, or --items-file");
401
+ process.exit(1);
402
+ }
403
+
404
+ const lines = [];
405
+ const cartItemList = [];
406
+ for (const spec of itemSpecs) {
407
+ const kw = spec.keyword;
408
+ if (!kw) continue;
409
+ const line = await resolveProductLine(cfg, {
410
+ keyword: kw,
411
+ shopId,
412
+ siteId,
413
+ quantity: Number(spec.quantity ?? quantity),
414
+ unit: String(spec.unit ?? unit),
415
+ });
416
+ lines.push(line);
417
+ cartItemList.push({
418
+ skuCode: line.skuCode,
419
+ skuName: line.skuName,
420
+ skuQty: line.skuQty,
421
+ skuSaleUnit: line.skuSaleUnit,
422
+ listPrice: line.salePrice,
423
+ salePrice: line.salePrice,
424
+ unitRate: line.unitRate,
425
+ selectFlag: true,
426
+ });
427
+ }
428
+
429
+ const cartBody = normalizeAddCartBody(
430
+ JSON.stringify({ cartItemList, cartModel, shopId, siteId }),
431
+ cfg,
432
+ );
433
+ const cartUrl = http.moduleUrl("ORDER", "/app/order/cart");
434
+ let r = await http.request(cartUrl, { method: "POST", headers, body: cartBody });
435
+ if (!r.ok || !biz.isBizSuccess(r.json)) {
436
+ envelope(false, "cart failed", { upstream: r.json, lines }, 2);
437
+ return;
438
+ }
439
+
440
+ const cartRows =
441
+ r.json && typeof r.json === "object" && r.json.data && typeof r.json.data === "object"
442
+ ? /** @type {{cartItemList?:Record<string,unknown>[]}} */ (r.json.data).cartItemList
443
+ : [];
444
+ const byCode = new Map();
445
+ if (Array.isArray(cartRows)) {
446
+ for (const row of cartRows) {
447
+ const code = String(/** @type {{skuCode?:string}} */ (row).skuCode || "");
448
+ if (code) byCode.set(code, row);
449
+ }
450
+ }
451
+
452
+ const itemVOList = [];
453
+ for (const line of lines) {
454
+ const row = byCode.get(line.skuCode) || {};
455
+ const addTime = row.addTime != null ? String(row.addTime) : "";
456
+ if (!addTime) {
457
+ envelope(false, `missing addTime for ${line.skuCode} after cart`, { upstream: r.json, lines }, 2);
458
+ return;
459
+ }
460
+ itemVOList.push({
461
+ skuId: line.skuId,
462
+ skuCode: line.skuCode,
463
+ skuName: line.skuName,
464
+ skuQty: line.skuQty,
465
+ skuSaleUnit: line.skuSaleUnit,
466
+ salePrice: line.salePrice,
467
+ listPrice: line.listPrice,
468
+ unitRate: line.unitRate,
469
+ addTime,
470
+ stockQty: line.stockQty,
471
+ minSaleUnit: line.skuSaleUnit,
472
+ selectFlag: true,
473
+ status: 0,
474
+ canBuyFlag: true,
475
+ });
476
+ }
477
+
478
+ const preBody = JSON.stringify({
479
+ headerVO: { shopId, siteId },
480
+ itemVOList,
481
+ balanceFlag: false,
482
+ userPointFlag: false,
483
+ });
484
+ const preUrl = http.moduleUrl("ORDER", "/app/order/preSettleOrder");
485
+ r = await http.request(preUrl, { method: "POST", headers, body: preBody });
486
+ if (!r.ok || !biz.isBizSuccess(r.json)) {
487
+ envelope(false, "pre-settle failed", { upstream: r.json, lines }, 2);
488
+ return;
489
+ }
490
+
491
+ const pdata =
492
+ r.json && typeof r.json === "object" && r.json.data && typeof r.json.data === "object"
493
+ ? r.json.data
494
+ : null;
495
+ const preSettleId = pdata && /** @type {{preSettleId?:string}} */ (pdata).preSettleId;
496
+ const headerVO = pdata && /** @type {{headerVO?:unknown}} */ (pdata).headerVO;
497
+ const payAmt =
498
+ pdata &&
499
+ /** @type {{headerVO?:{payAmt?:unknown,totalPayAmt?:unknown}}} */ (pdata).headerVO &&
500
+ (/** @type {{payAmt?:unknown}} */ (
501
+ /** @type {{headerVO?:{payAmt?:unknown}}} */ (pdata).headerVO
502
+ ).payAmt ??
503
+ /** @type {{totalPayAmt?:unknown}} */ (
504
+ /** @type {{headerVO?:{totalPayAmt?:unknown}}} */ (pdata).headerVO
505
+ ).totalPayAmt);
506
+
507
+ if (opts.preSettleOnly) {
508
+ envelope(
509
+ true,
510
+ "success",
511
+ {
512
+ lines,
513
+ preSettleId,
514
+ payAmt,
515
+ upstream: r.json,
516
+ hint: "Call order confirm with preSettleId + slim headerVO/itemVOList (skuId per line) after user confirms.",
517
+ },
518
+ 0,
519
+ );
520
+ return;
521
+ }
522
+
523
+ if (!preSettleId) {
524
+ envelope(false, "missing preSettleId", { upstream: r.json, lines }, 2);
525
+ return;
526
+ }
527
+
528
+ const preItems = pdata && /** @type {{itemVOList?:unknown[]}} */ (pdata).itemVOList;
529
+ const confirmItems = Array.isArray(preItems)
530
+ ? preItems.map((it) => {
531
+ const row = /** @type {Record<string,unknown>} */ (it);
532
+ const match = lines.find((l) => l.skuCode === row.skuCode);
533
+ return {
534
+ skuId: match?.skuId ?? row.skuId,
535
+ skuCode: row.skuCode,
536
+ skuQty: row.skuQty,
537
+ skuSaleUnit: row.skuSaleUnit,
538
+ };
539
+ })
540
+ : itemVOList.map((it) => ({
541
+ skuId: it.skuId,
542
+ skuCode: it.skuCode,
543
+ skuQty: it.skuQty,
544
+ skuSaleUnit: it.skuSaleUnit,
545
+ }));
546
+
547
+ const slimHeader =
548
+ headerVO && typeof headerVO === "object"
549
+ ? {
550
+ shopId: /** @type {{shopId?:number}} */ (headerVO).shopId ?? shopId,
551
+ siteId: Number(/** @type {{siteId?:unknown}} */ (headerVO).siteId ?? siteId),
552
+ receiverName: /** @type {{receiverName?:string}} */ (headerVO).receiverName,
553
+ receiverPhone: /** @type {{receiverPhone?:string}} */ (headerVO).receiverPhone,
554
+ receiverAddressProvince: /** @type {{receiverAddressProvince?:string}} */ (headerVO)
555
+ .receiverAddressProvince,
556
+ receiverAddressCity: /** @type {{receiverAddressCity?:string}} */ (headerVO).receiverAddressCity,
557
+ receiverAddressArea: /** @type {{receiverAddressArea?:string}} */ (headerVO).receiverAddressArea,
558
+ receiverAddressDetail: /** @type {{receiverAddressDetail?:string}} */ (headerVO)
559
+ .receiverAddressDetail,
560
+ }
561
+ : { shopId, siteId };
562
+
563
+ const confirmBody = JSON.stringify({
564
+ preSettleId,
565
+ headerVO: slimHeader,
566
+ itemVOList: confirmItems,
567
+ balanceFlag: false,
568
+ userPointFlag: false,
569
+ validateStockFlag: true,
570
+ repeatFlag: false,
571
+ });
572
+ const confirmUrl = http.moduleUrl("ORDER", "/app/order/confirmOrder");
573
+ r = await http.request(confirmUrl, { method: "POST", headers, body: confirmBody });
574
+ if (!r.ok || !biz.isBizSuccess(r.json)) {
575
+ envelope(false, "confirm failed", { upstream: r.json, lines, preSettleId, payAmt }, 2);
576
+ return;
577
+ }
578
+
579
+ const orderData =
580
+ r.json && typeof r.json === "object" && r.json.data && typeof r.json.data === "object"
581
+ ? r.json.data
582
+ : {};
583
+ const orderId = /** @type {{orderId?:string}} */ (orderData).orderId;
584
+
585
+ const memberUrl = http.moduleUrl("DEFAULT", "/member/getInfo/V2");
586
+ const h2 = { ...headers };
587
+ delete h2["Content-Type"];
588
+ const uinfo = await http.request(memberUrl, { method: "GET", headers: h2, body: null });
589
+ let phone = String(cfg.phone || "");
590
+ let nick = "";
591
+ let img = "";
592
+ if (uinfo.ok && biz.isBizSuccess(uinfo.json) && uinfo.json && typeof uinfo.json === "object") {
593
+ const d = /** @type {{data?:Record<string,unknown>}} */ (uinfo.json).data;
594
+ if (d) {
595
+ phone = String(d.memberPhone || phone);
596
+ nick = String(d.memberNick || "");
597
+ img = String(d.headerImg || "");
598
+ }
599
+ }
600
+ const enc = encodeURIComponent;
601
+ const payUrlStr = `https://dhcmall.ifoodbuy.com/H5/#/replacePay?phone=${enc(phone)}&orderid=${enc(String(orderId || ""))}&img=${enc(img)}&nick=${enc(nick)}&siteId=${siteId}&shopId=${shopId}&share=true`;
602
+
603
+ envelope(
604
+ true,
605
+ "success",
606
+ {
607
+ lines,
608
+ preSettleId,
609
+ payAmt,
610
+ orderId,
611
+ payUrl: payUrlStr,
612
+ upstream: r.json,
613
+ },
614
+ 0,
615
+ );
616
+ }
617
+
618
+ module.exports = { preSettle, confirm, payUrl, quick, checkout, list, cancel };