@tbox.cn/app-module-member 0.2.0 → 0.8.7

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.
Files changed (63) hide show
  1. package/AGENTS.md +16 -0
  2. package/README.md +99 -11
  3. package/dist/chunk-EPQEF43P.js +1581 -0
  4. package/dist/chunk-QWGRWD5C.js +766 -0
  5. package/dist/client/index.d.ts +2 -2
  6. package/dist/client/index.js +1 -1
  7. package/dist/index.d.ts +2 -3
  8. package/dist/index.js +12 -2
  9. package/dist/member-372B74DY.css +1740 -0
  10. package/dist/server/index.d.ts +657 -286
  11. package/dist/server/index.js +11 -1
  12. package/package.json +25 -16
  13. package/skills/member/SKILL.md +141 -0
  14. package/skills/member/references/enrollment.md +34 -0
  15. package/skills/member/references/points-and-card.md +24 -0
  16. package/src/client/cards/index.ts +10 -0
  17. package/src/client/cards/member-card-summary/index.tsx +375 -0
  18. package/src/client/cards/member-enrollment-cta/index.tsx +61 -0
  19. package/src/client/cards/member-enrollment-form/index.tsx +181 -0
  20. package/src/client/cards/member-enrollment-status/index.tsx +65 -0
  21. package/src/client/cards/member-info/index.tsx +60 -0
  22. package/src/client/cards/member-login-cta/index.tsx +81 -0
  23. package/src/client/cards/member-point-usage-list/index.tsx +53 -0
  24. package/src/client/cards/view.ts +39 -0
  25. package/src/client/components/ImageWithFallback.tsx +38 -0
  26. package/src/client/index.ts +6 -4
  27. package/src/client/styles/member.css +1740 -0
  28. package/src/client/types/css.d.ts +2 -0
  29. package/src/server/action.ts +348 -0
  30. package/src/server/cards/index.ts +7 -7
  31. package/src/server/cards/member-card-summary/meta.ts +26 -5
  32. package/src/server/cards/member-card-summary/samples.ts +272 -0
  33. package/src/server/cards/member-enrollment-cta/meta.ts +19 -0
  34. package/src/server/cards/member-enrollment-form/meta.ts +10 -6
  35. package/src/server/cards/member-enrollment-status/meta.ts +5 -6
  36. package/src/server/cards/member-info/meta.ts +1 -1
  37. package/src/server/cards/member-login-cta/meta.ts +16 -0
  38. package/src/server/cards/member-point-usage-list/meta.ts +2 -3
  39. package/src/server/handler.ts +159 -119
  40. package/src/server/index.ts +129 -50
  41. package/src/server/ports.ts +66 -0
  42. package/src/server/service.ts +94 -80
  43. package/src/server/status.ts +87 -0
  44. package/src/server/subject-binding.ts +19 -0
  45. package/src/server/tool.ts +469 -99
  46. package/tbox.module.json +124 -0
  47. package/tests/cards-render.test.tsx +526 -0
  48. package/tests/enrollment.test.ts +121 -0
  49. package/tests/ports-resolve.test.ts +95 -0
  50. package/tests/preference-memory.test.ts +86 -0
  51. package/tests/samples.test.ts +61 -0
  52. package/tests/service.test.ts +593 -35
  53. package/tests/skills.test.ts +65 -0
  54. package/tests/snapshot-service.test.ts +75 -0
  55. package/tests/status.test.ts +127 -0
  56. package/tests/view.test.ts +33 -0
  57. package/tsup.config.ts +9 -1
  58. package/dist/chunk-JGZGLFO6.js +0 -111
  59. package/dist/chunk-ZV6B36AO.js +0 -613
  60. package/src/client/cards/index.tsx +0 -135
  61. package/src/server/cards/member-coupon-detail/meta.ts +0 -19
  62. package/src/server/cards/member-coupon-list/meta.ts +0 -18
  63. package/tbox.component.json +0 -28
@@ -0,0 +1,1581 @@
1
+ // src/server/index.ts
2
+ import { loadModuleSkills, withCardEmissionHint, withEarlyExecution } from "@tbox.cn/app-sdk/server";
3
+ import { MEMBER_SNAPSHOT_SERVICE } from "@tbox.cn/app-contracts-mall";
4
+
5
+ // src/server/cards/member-info/meta.ts
6
+ import { memberInfoCardDataSchema } from "@tbox.cn/app-contracts-mall";
7
+ var meta = {
8
+ cardType: "member-info",
9
+ dataSchema: memberInfoCardDataSchema,
10
+ displayName: "\u4F1A\u5458\u4FE1\u606F",
11
+ description: "\u4F1A\u5458\u8D26\u6237\u72B6\u6001\u4E0E\u4E2A\u4EBA\u8D44\u6599",
12
+ sampleData: {
13
+ userId: "u_1001",
14
+ authState: "authenticated",
15
+ state: "active",
16
+ displayName: "\u793A\u4F8B\u4F1A\u5458",
17
+ labels: [{ labelRef: "member", name: "\u4F1A\u5458" }]
18
+ },
19
+ schemaVersion: 1
20
+ };
21
+ var meta_default = meta;
22
+
23
+ // src/server/cards/member-card-summary/meta.ts
24
+ import { memberCardSummaryCardDataSchema } from "@tbox.cn/app-contracts-mall";
25
+
26
+ // src/server/cards/member-card-summary/samples.ts
27
+ function cardFace(from, to) {
28
+ const marks = '<rect x="58" y="52" width="210" height="42" rx="8" fill="%23000000" opacity="0.14"/><rect x="58" y="130" width="132" height="26" rx="6" fill="%23000000" opacity="0.10"/>';
29
+ return `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="672" height="347"><defs><linearGradient id="f" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="%23${from}"/><stop offset="1" stop-color="%23${to}"/></linearGradient></defs><rect width="672" height="347" fill="url(%23f)"/><circle cx="548" cy="60" r="150" fill="%23ffffff" opacity="0.16"/>` + marks + "</svg>";
30
+ }
31
+ var FACE = {
32
+ blue: cardFace("dce8fb", "aecdf5"),
33
+ silver: cardFace("eef1f7", "c4cddf"),
34
+ gold: cardFace("f9ecca", "e3c283"),
35
+ black: cardFace("1b1e26", "4a412c")
36
+ };
37
+ var POINTS_RULE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="750" height="1600"><rect width="750" height="1600" fill="%23fffdf8"/><rect x="60" y="80" width="380" height="44" rx="10" fill="%23e2c78f"/><rect x="60" y="180" width="630" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="230" width="560" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="280" width="610" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="700" width="630" height="24" rx="8" fill="%23ece7dd"/><rect x="60" y="1500" width="400" height="24" rx="8" fill="%23ece7dd"/></svg>';
38
+ var ICON_COLORS = {
39
+ blue: { fg: "254077", bg: "dce8fb" },
40
+ silver: { fg: "303443", bg: "e8ecf3" },
41
+ gold: { fg: "683d08", bg: "f3e3b8" },
42
+ black: { fg: "e9d7ac", bg: "2a2d35" }
43
+ };
44
+ function benefitIcon(variant, benefitRef) {
45
+ const { fg, bg } = ICON_COLORS[variant];
46
+ let hash = 0;
47
+ for (const ch of benefitRef) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
48
+ const shape = hash % 4;
49
+ const drawn = shape === 0 ? `<circle cx="24" cy="24" r="10" fill="%23${fg}"/>` : shape === 1 ? `<circle cx="24" cy="24" r="10" fill="none" stroke="%23${fg}" stroke-width="5"/>` : shape === 2 ? `<rect x="15" y="15" width="18" height="18" rx="4" fill="%23${fg}"/>` : `<path d="M24 12 L36 34 L12 34 Z" fill="%23${fg}"/>`;
50
+ return `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><rect width="48" height="48" rx="12" fill="%23${bg}"/>${drawn}</svg>`;
51
+ }
52
+ function benefitsOf(variant) {
53
+ const BASE_NAMES = [
54
+ { ref: "benefit:points", name: "\u6D88\u8D39\u79EF\u5206" },
55
+ { ref: "benefit:gifts", name: "\u79EF\u5206\u6362\u793C" },
56
+ { ref: "benefit:events", name: "\u4F1A\u5458\u6D3B\u52A8" },
57
+ { ref: "benefit:express", name: "\u987A\u4E3085\u6298" },
58
+ { ref: "benefit:points-parking", name: "\u79EF\u5206\u5151\u505C\u8F66" },
59
+ { ref: "benefit:storage", name: "\u7269\u54C1\u5B58\u50A8" },
60
+ { ref: "benefit:care", name: "\u6696\u5FC3\u5B88\u62A4" },
61
+ { ref: "benefit:pet", name: "\u840C\u5BA0\u9A7F\u7AD9" },
62
+ { ref: "benefit:service", name: "\u4E13\u5C5E\u5BA2\u670D" },
63
+ { ref: "benefit:umbrella", name: "\u96E8\u4F1E\u79DF\u501F" },
64
+ { ref: "benefit:wheelchair", name: "\u8F6E\u6905\u79DF\u501F" },
65
+ { ref: "benefit:stroller", name: "\u5A74\u513F\u8F66\u79DF\u501F" },
66
+ { ref: "benefit:birthday", name: "\u751F\u65E5\u793C\u9047" },
67
+ { ref: "benefit:wifi", name: "\u514D\u8D39WIFI" }
68
+ ];
69
+ const items = BASE_NAMES.map((b) => ({
70
+ benefitRef: b.ref,
71
+ name: b.name,
72
+ iconUrl: benefitIcon(variant, b.ref)
73
+ }));
74
+ if (variant === "gold" || variant === "black") {
75
+ return [
76
+ {
77
+ benefitRef: "benefit:parking",
78
+ name: "\u514D\u8D39\u505C\u8F66",
79
+ iconUrl: benefitIcon(variant, "benefit:parking"),
80
+ ...variant === "gold" ? { description: "\u53EF\u514D1\u5C0F\u65F6" } : { description: "\u53EF\u514D2\u5C0F\u65F6" }
81
+ },
82
+ ...items
83
+ ];
84
+ }
85
+ return items;
86
+ }
87
+ var PROGRESS_SHORT = {
88
+ daySpentCents: 12345,
89
+ yearSpentCents: 5e5,
90
+ percent: 25,
91
+ message: "\u4ECA\u65E5\u5DF2\u6D88\u8D39123.45\u5143"
92
+ };
93
+ var samples = [
94
+ {
95
+ label: "\u5361\u9762\xB7\u91D1\u5361",
96
+ note: "\u57FA\u51C6\u5F62\u6001\uFF1A\u5361\u540D\u4E0E\u300C\u6211\u7684\u79EF\u5206\u300D\u7531\u5361\u56FE\u81EA\u5E26\uFF0C\u524D\u7AEF\u53EA\u53E0\u79EF\u5206\u6570\u3002\u5E26 pointsRuleImageUrl \u65F6\u89E6\u53D1\u5668\u662F\u76D6\u5728\u5361\u56FE\u81EA\u5370\u6309\u94AE\u4E0A\u7684\u900F\u660E\u70ED\u533A\uFF08\u89C6\u89C9\u4E0A\u770B\u4E0D\u51FA\uFF0C\u5F39\u5C42\u6001\u9700\u624B\u52A8\u70B9\u5B83\uFF09",
97
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
98
+ data: {
99
+ userId: "u_1001",
100
+ cardRef: "card:u_1001",
101
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
102
+ status: "active",
103
+ availablePoints: 860,
104
+ cardImageUrl: FACE.gold,
105
+ pointsRuleImageUrl: POINTS_RULE_IMAGE,
106
+ presentation: { variant: "gold", labelsEmbedded: true },
107
+ progress: PROGRESS_SHORT,
108
+ benefits: benefitsOf("gold")
109
+ }
110
+ },
111
+ {
112
+ label: "\u5361\u9762\xB7\u84DD\u5361",
113
+ note: "variant=blue\uFF08\u5165\u95E8\u7B49\u7EA7\uFF09\u2014\u2014\u53E0\u5B57\u5BF9\u6BD4\u5EA6\u4E4B\u5916\uFF0C\u6743\u76CA 14 \u9879\u4E14\u65E0\u300C\u514D\u8D39\u505C\u8F66\u300D\uFF0C\u4E0E\u91D1/\u9ED1\u7684 15 \u9879\u5BF9\u7167",
114
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
115
+ data: {
116
+ userId: "u_1001",
117
+ cardRef: "card:u_1001",
118
+ cardDisplay: "\u60A6\u4EAB\u5361",
119
+ status: "active",
120
+ availablePoints: 120,
121
+ cardImageUrl: FACE.blue,
122
+ presentation: { variant: "blue", labelsEmbedded: true },
123
+ progress: { daySpentCents: 0, yearSpentCents: 8800, percent: 4, message: "\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D3988.00\u5143" },
124
+ benefits: benefitsOf("blue")
125
+ }
126
+ },
127
+ {
128
+ label: "\u5361\u9762\xB7\u94F6\u5361",
129
+ note: "variant=silver\u2014\u2014\u6D45\u5E95\u6700\u5BB9\u6613\u538B\u4E0D\u4F4F\u767D\u5B57\uFF1B\u6743\u76CA\u540C\u4E3A 14 \u9879\uFF0C\u56FE\u6807\u662F\u94F6\u5361\u914D\u8272\u7248",
130
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
131
+ data: {
132
+ userId: "u_1001",
133
+ cardRef: "card:u_1001",
134
+ cardDisplay: "\u94F6\u5361\u4F1A\u5458",
135
+ status: "active",
136
+ availablePoints: 3400,
137
+ cardImageUrl: FACE.silver,
138
+ presentation: { variant: "silver", labelsEmbedded: true },
139
+ progress: { daySpentCents: 4500, yearSpentCents: 12e4, percent: 48, message: "\u4ECA\u65E5\u5DF2\u6D88\u8D3945.00\u5143\uFF0C\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D391200.00\u5143" },
140
+ benefits: benefitsOf("silver")
141
+ }
142
+ },
143
+ {
144
+ label: "\u5361\u9762\xB7\u9ED1\u91D1\u5361",
145
+ note: "variant=black\uFF08\u6700\u9AD8\u7B49\u7EA7\uFF09\u2014\u2014\u91D1\u8272\u63CF\u8FB9\u4E0E\u6DF1\u5E95\u7684\u5C42\u6B21\uFF1B15 \u9879\u6743\u76CA\uFF0C\u514D\u8D39\u505C\u8F66\u5E26\u300C\u53EF\u514D2\u5C0F\u65F6\u300D\u8BF4\u660E",
146
+ group: "\u4F1A\u5458\u5361\u9762\uFF08\u56DB\u6863 variant\uFF09",
147
+ data: {
148
+ userId: "u_1001",
149
+ cardRef: "card:u_1001",
150
+ cardDisplay: "\u9ED1\u91D1\u4F1A\u5458",
151
+ status: "active",
152
+ availablePoints: 128600,
153
+ cardImageUrl: FACE.black,
154
+ presentation: { variant: "black", labelsEmbedded: true },
155
+ progress: { daySpentCents: 98800, yearSpentCents: 24e5, percent: 100, message: "\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D3924000.00\u5143\uFF0C\u5DF2\u8FBE\u6700\u9AD8\u7B49\u7EA7" },
156
+ benefits: benefitsOf("black")
157
+ }
158
+ },
159
+ {
160
+ label: "\u5361\u9762\xB7\u5361\u56FE\u7F3A\u5931",
161
+ note: "presentation \u5728\u4F46 cardImageUrl \u7F3A\u7701\u2014\u2014\u9000\u5230\u6E10\u53D8\u515C\u5E95\u5E95\u7EB9\uFF0C\u53E0\u5B57\u6392\u5E03\u5E94\u4E0D\u53D8",
162
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
163
+ data: {
164
+ userId: "u_1001",
165
+ cardRef: "card:u_1001",
166
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
167
+ status: "active",
168
+ availablePoints: 860,
169
+ presentation: { variant: "gold", labelsEmbedded: true },
170
+ progress: PROGRESS_SHORT,
171
+ benefits: benefitsOf("gold")
172
+ }
173
+ },
174
+ {
175
+ label: "\u5361\u9762\xB7\u5916\u94FE\u52A0\u8F7D\u5931\u8D25",
176
+ note: "\u7EBF\u4E0A\u5361\u56FE 404 / \u65AD\u7F51\u7684\u5B9E\u51B5\u2014\u2014ImageWithFallback \u5360\u4F4D\u4E0D\u5E94\u628A\u5361\u9762\u6491\u53D8\u5F62",
177
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
178
+ data: {
179
+ userId: "u_1001",
180
+ cardRef: "card:u_1001",
181
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
182
+ status: "active",
183
+ availablePoints: 860,
184
+ cardImageUrl: "https://assets.example.com/member/gold-card.png",
185
+ presentation: { variant: "gold", labelsEmbedded: true },
186
+ progress: PROGRESS_SHORT,
187
+ benefits: benefitsOf("gold")
188
+ }
189
+ },
190
+ {
191
+ label: "\u5361\u9762\xB7\u8FDB\u5EA6\u957F\u6587\u6848",
192
+ note: "progress.message \u8D85 24 \u5B57\u89E6\u53D1 --scroll \u8DD1\u9A6C\u706F\uFF1Bpercent=0 \u65F6\u8FDB\u5EA6\u6761\u5E94\u53EF\u89C1\u8F68\u9053",
193
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
194
+ data: {
195
+ userId: "u_1001",
196
+ cardRef: "card:u_1001",
197
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
198
+ status: "active",
199
+ availablePoints: 860,
200
+ cardImageUrl: FACE.gold,
201
+ presentation: { variant: "gold", labelsEmbedded: true },
202
+ progress: {
203
+ daySpentCents: 0,
204
+ yearSpentCents: 0,
205
+ percent: 0,
206
+ message: "\u4ECA\u5E74\u8FD8\u6CA1\u6709\u6D88\u8D39\u8BB0\u5F55\uFF0C\u7D2F\u8BA1\u6D88\u8D39\u6EE15000.00\u5143\u53EF\u5347\u7EA7\u4E3A\u94F6\u5361\u4F1A\u5458"
207
+ },
208
+ benefits: benefitsOf("gold")
209
+ }
210
+ },
211
+ {
212
+ label: "\u5361\u9762\xB7\u65E0\u8FDB\u5EA6\u65E0\u6743\u76CA",
213
+ note: "progress \u4E0E benefits \u540C\u65F6\u7F3A\u7701\u2014\u2014\u53EA\u5269\u5361\u9762 + \u504F\u597D\u5F15\u5BFC\uFF0C\u68C0\u67E5\u5E95\u90E8\u6536\u53E3",
214
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
215
+ data: {
216
+ userId: "u_1001",
217
+ cardRef: "card:u_1001",
218
+ cardDisplay: "\u60A6\u4EAB\u5361",
219
+ status: "active",
220
+ availablePoints: 0,
221
+ cardImageUrl: FACE.blue,
222
+ presentation: { variant: "blue", labelsEmbedded: true }
223
+ }
224
+ },
225
+ {
226
+ label: "\u5361\u9762\xB7\u6743\u76CA\u56FE\u6807\u5931\u8D25",
227
+ note: "iconUrl \u52A0\u8F7D\u5931\u8D25 \u2192 \u6309\u6743\u76CA\u540D\u5339\u914D\u7684\u515C\u5E95\u56FE\u6807\uFF08\u505C\u8F66/\u79EF\u5206/\u6D3B\u52A8/\u5176\u5B83\u56DB\u6761\u5206\u652F\uFF09",
228
+ group: "\u5361\u9762\u7D20\u6750\u5206\u652F",
229
+ data: {
230
+ userId: "u_1001",
231
+ cardRef: "card:u_1001",
232
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
233
+ status: "active",
234
+ availablePoints: 860,
235
+ cardImageUrl: FACE.gold,
236
+ presentation: { variant: "gold", labelsEmbedded: true },
237
+ progress: PROGRESS_SHORT,
238
+ benefits: [
239
+ { benefitRef: "b1", name: "\u514D\u8D39\u505C\u8F66", iconUrl: "https://assets.example.com/x.png" },
240
+ { benefitRef: "b2", name: "\u6D88\u8D39\u79EF\u5206", iconUrl: "https://assets.example.com/x.png" },
241
+ { benefitRef: "b3", name: "\u4F1A\u5458\u6D3B\u52A8", iconUrl: "https://assets.example.com/x.png" },
242
+ { benefitRef: "b4", name: "\u751F\u65E5\u793C\u9047", iconUrl: "https://assets.example.com/x.png" }
243
+ ]
244
+ }
245
+ },
246
+ {
247
+ label: "\u5361\u9762\xB7\u5355\u6761\u504F\u597D",
248
+ note: "preferencePrompts \u663E\u5F0F\u7ED9\u4E00\u9879\u2014\u2014\u7F3A\u7701\u65F6\u7EC4\u4EF6\u515C\u5E95\u6210 contracts \u56FA\u5B9A\u4E09\u9879\uFF0C\u5355\u9879\u65F6\u770B\u6309\u94AE\u662F\u5426\u88AB\u62C9\u4F38",
249
+ group: "\u504F\u597D\u5F15\u5BFC\u5206\u652F",
250
+ data: {
251
+ userId: "u_1001",
252
+ cardRef: "card:u_1001",
253
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
254
+ status: "active",
255
+ availablePoints: 860,
256
+ cardImageUrl: FACE.gold,
257
+ presentation: { variant: "gold", labelsEmbedded: true },
258
+ progress: PROGRESS_SHORT,
259
+ benefits: benefitsOf("gold"),
260
+ preferencePrompts: [{ value: "\u6211\u5E38\u6765\u5403\u7F8E\u98DF", query: "\u63A8\u8350\u503C\u5F97\u53BB\u7684\u9910\u996E\u7F8E\u98DF\u95E8\u5E97" }]
261
+ }
262
+ },
263
+ {
264
+ label: "\u5361\u9762\xB7\u5386\u53F2\u5361",
265
+ note: "isHistory\u2014\u2014\u504F\u597D\u5F15\u5BFC\u6309\u94AE\u5168\u7981\u7528\uFF08\u70B9\u4E86\u4E5F\u4E0D\u4F1A\u53D1\u6D88\u606F\uFF09\uFF0C\u5361\u9762\u4E0E\u6743\u76CA\u533A\u7167\u5E38\u6E32\u67D3",
266
+ group: "\u504F\u597D\u5F15\u5BFC\u5206\u652F",
267
+ data: {
268
+ userId: "u_1001",
269
+ cardRef: "card:u_1001",
270
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
271
+ status: "active",
272
+ availablePoints: 860,
273
+ cardImageUrl: FACE.gold,
274
+ presentation: { variant: "gold", labelsEmbedded: true },
275
+ progress: PROGRESS_SHORT,
276
+ benefits: benefitsOf("gold")
277
+ },
278
+ isHistory: true
279
+ }
280
+ ];
281
+
282
+ // src/server/cards/member-card-summary/meta.ts
283
+ var meta2 = {
284
+ cardType: "member-card-summary",
285
+ dataSchema: memberCardSummaryCardDataSchema,
286
+ displayName: "\u4F1A\u5458\u5361\u9762",
287
+ description: "\u4F1A\u5458\u5361\u4FE1\u606F\uFF08\u771F\u5B9E\u5361\u540D\u3001\u52A8\u6001\u5361\u9762\u3001\u79EF\u5206\u4E0E\u6D88\u8D39\u8FDB\u5EA6\u6458\u8981\uFF09",
288
+ sampleData: {
289
+ userId: "u_1001",
290
+ cardRef: "card:u_1001",
291
+ cardDisplay: "\u91D1\u5361\u4F1A\u5458",
292
+ status: "active",
293
+ availablePoints: 860,
294
+ cardImageUrl: "https://assets.example.com/member/gold-card.png",
295
+ pointsRuleImageUrl: "https://assets.example.com/member/points-rule.jpg",
296
+ presentation: { variant: "gold", labelsEmbedded: true },
297
+ progress: {
298
+ daySpentCents: 12345,
299
+ yearSpentCents: 5e5,
300
+ percent: 25,
301
+ message: "\u4ECA\u65E5\u5DF2\u6D88\u8D39123.45\u5143\uFF0C\u5E74\u7D2F\u8BA1\u5DF2\u6D88\u8D395000.00\u5143"
302
+ },
303
+ benefits: [
304
+ { benefitRef: "benefit:parking", name: "\u514D\u8D39\u505C\u8F66", iconUrl: "https://assets.example.com/member/parking.png" },
305
+ { benefitRef: "benefit:points", name: "\u6D88\u8D39\u79EF\u5206", iconUrl: "https://assets.example.com/member/points.png" },
306
+ { benefitRef: "benefit:gifts", name: "\u79EF\u5206\u6362\u793C", iconUrl: "https://assets.example.com/member/gifts.png" },
307
+ { benefitRef: "benefit:events", name: "\u4F1A\u5458\u6D3B\u52A8", iconUrl: "https://assets.example.com/member/events.png" }
308
+ ],
309
+ preferencePrompts: [
310
+ { value: "\u6211\u5E38\u6765\u4E70\u8863\u670D", query: "\u6211\u5E38\u6765\u4E70\u8863\u670D" },
311
+ { value: "\u6211\u5E38\u5E26\u5A03\u6765\u73A9", query: "\u6211\u5E38\u5E26\u5A03\u6765\u73A9" },
312
+ { value: "\u6211\u5E38\u6765\u5403\u7F8E\u98DF", query: "\u6211\u5E38\u6765\u5403\u7F8E\u98DF" }
313
+ ]
314
+ },
315
+ samples,
316
+ schemaVersion: 5,
317
+ migrate: (oldData) => oldData
318
+ };
319
+ var meta_default2 = meta2;
320
+
321
+ // src/server/cards/member-point-usage-list/meta.ts
322
+ import { memberPointUsageListCardDataSchema } from "@tbox.cn/app-contracts-mall";
323
+ var meta3 = {
324
+ cardType: "member-point-usage-list",
325
+ dataSchema: memberPointUsageListCardDataSchema,
326
+ displayName: "\u79EF\u5206\u4F59\u989D\u4E0E\u6D41\u6C34",
327
+ description: "\u4F1A\u5458\u79EF\u5206\u4F59\u989D\u3001\u5382\u5546\u4F1A\u5458\u6807\u7B7E\u4E0E\u53D8\u52A8\u6D41\u6C34\uFF08\u5438\u6536\u6559\u5B66 points \u5361\uFF09",
328
+ sampleData: {
329
+ userId: "u_1001",
330
+ balance: 860,
331
+ membershipLabel: "\u60A6\u4EAB\u5361",
332
+ items: [{ usageRecordRef: "r1", title: "\u6D88\u8D39\u5956\u52B1", amount: "+100", usedAt: "2026-08-13T00:00:00Z" }],
333
+ total: 1
334
+ },
335
+ schemaVersion: 1
336
+ };
337
+ var meta_default3 = meta3;
338
+
339
+ // src/server/cards/member-enrollment-form/meta.ts
340
+ import { memberEnrollmentFormCardDataSchema } from "@tbox.cn/app-contracts-mall";
341
+ var meta4 = {
342
+ cardType: "member-enrollment-form",
343
+ dataSchema: memberEnrollmentFormCardDataSchema,
344
+ displayName: "\u5165\u4F1A\u8868\u5355",
345
+ description: "\u4F1A\u5458\u5165\u4F1A\u8868\u5355\uFF08\u652F\u4ED8\u5B9D\u6388\u6743\u9884\u586B + \u624B\u52A8\u964D\u7EA7\uFF1B\u52A8\u4F5C member.request-alipay-authorization / member.enroll\uFF09",
346
+ sampleData: {
347
+ userId: "u_1001",
348
+ state: "not_enrolled",
349
+ formVersion: "v2026.08",
350
+ fields: [
351
+ { fieldId: "mobile", label: "\u624B\u673A\u53F7", type: "tel", required: true, placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7" },
352
+ { fieldId: "displayName", label: "\u59D3\u540D", type: "text", required: false }
353
+ ],
354
+ consents: [
355
+ { consentId: "privacy", version: "1", title: "\u9690\u79C1\u653F\u7B56", required: true },
356
+ { consentId: "membership-terms", version: "1", title: "\u4F1A\u5458\u670D\u52A1\u6761\u6B3E", required: false }
357
+ ],
358
+ authorization: { status: "required" }
359
+ },
360
+ schemaVersion: 2
361
+ };
362
+ var meta_default4 = meta4;
363
+
364
+ // src/server/cards/member-enrollment-status/meta.ts
365
+ import { memberEnrollmentStatusCardDataSchema } from "@tbox.cn/app-contracts-mall";
366
+ var meta5 = {
367
+ cardType: "member-enrollment-status",
368
+ dataSchema: memberEnrollmentStatusCardDataSchema,
369
+ displayName: "\u5165\u4F1A\u72B6\u6001",
370
+ description: "\u5165\u4F1A\u7ED3\u679C\u72B6\u6001\uFF08pending \u6001\u9644\u663E\u5F0F\u5237\u65B0\u6309\u94AE\uFF0C\u52A8\u4F5C member.query-enrollment-status\uFF09",
371
+ sampleData: {
372
+ userId: "u_1001",
373
+ attemptRef: "att_1",
374
+ state: "pending_confirmation",
375
+ message: "\u5382\u5546\u786E\u8BA4\u4E2D\uFF0C\u53EF\u7A0D\u540E\u518D\u5237\u65B0"
376
+ },
377
+ schemaVersion: 2
378
+ };
379
+ var meta_default5 = meta5;
380
+
381
+ // src/server/cards/member-enrollment-cta/meta.ts
382
+ import { memberEnrollmentCtaCardDataSchema } from "@tbox.cn/app-contracts-mall";
383
+ var meta6 = {
384
+ cardType: "member-enrollment-cta",
385
+ dataSchema: memberEnrollmentCtaCardDataSchema,
386
+ displayName: "\u5F00\u5361\u5165\u4F1A",
387
+ description: "\u5F00\u5361\u5165\u4F1A\u5F15\u5BFC\u5361\u2014\u2014\u70B9\u51FB\u540E\u8DF3\u8F6C alipays \u94FE\u63A5\u5B8C\u6210\u5165\u4F1A",
388
+ sampleData: {
389
+ userId: "u_1001",
390
+ authState: "authenticated",
391
+ state: "not_enrolled",
392
+ enrollUrl: "alipays://platformapi/startapp?appId=demo&page=v/index/index",
393
+ title: "\u5165\u4F1A\u540E\u7EE7\u7EED"
394
+ },
395
+ schemaVersion: 2
396
+ };
397
+ var meta_default6 = meta6;
398
+
399
+ // src/server/cards/member-login-cta/meta.ts
400
+ import { memberLoginCtaCardDataSchema } from "@tbox.cn/app-contracts-mall";
401
+ var meta7 = {
402
+ cardType: "member-login-cta",
403
+ dataSchema: memberLoginCtaCardDataSchema,
404
+ displayName: "\u767B\u5F55\u5F15\u5BFC",
405
+ description: "\u672A\u767B\u5F55\u5F15\u5BFC\u5361\u2014\u2014\u70B9\u51FB\u8DF3\u8F6C\u5546\u573A\u5C0F\u7A0B\u5E8F\u767B\u5F55\u9875\uFF0C\u767B\u5F55\u540E\u56DE\u5230\u667A\u80FD\u4F53\u7EE7\u7EED",
406
+ sampleData: {
407
+ loginUrl: "alipays://platformapi/startapp?appId=demo&page=v/login/index",
408
+ title: "\u767B\u5F55\u540E\u7EE7\u7EED"
409
+ },
410
+ schemaVersion: 1
411
+ };
412
+ var meta_default7 = meta7;
413
+
414
+ // src/server/tool.ts
415
+ import { createTool } from "@tbox.cn/app-sdk/server";
416
+ import { requireToolRequestContext } from "@tbox.cn/app-sdk/server";
417
+ import { MEMBER_PREFERENCE_PROMPTS } from "@tbox.cn/app-contracts-mall";
418
+ import { classifyMemberOutcome as classifyMemberOutcome2, memberGateCardOf } from "@tbox.cn/app-contracts-mall";
419
+
420
+ // src/server/status.ts
421
+ import { classifyMemberOutcome } from "@tbox.cn/app-contracts-mall";
422
+
423
+ // src/server/ports.ts
424
+ import {
425
+ MEMBER_ACCOUNT_SERVICE,
426
+ MEMBER_BENEFITS_SERVICE,
427
+ MEMBER_CARD_SERVICE,
428
+ MEMBER_ENROLLMENT_SERVICE,
429
+ MEMBER_PROFILE_SERVICE
430
+ } from "@tbox.cn/app-contracts-mall";
431
+ import { mallScopeOf } from "@tbox.cn/app-contracts-mall";
432
+
433
+ // src/server/service.ts
434
+ var InMemoryMemberService = class {
435
+ enrollUrl = "";
436
+ balances = /* @__PURE__ */ new Map();
437
+ idempotentKeys = /* @__PURE__ */ new Set();
438
+ usages = [];
439
+ profiles = /* @__PURE__ */ new Map();
440
+ enrollmentAttempts = /* @__PURE__ */ new Map();
441
+ pendingConfirmations = /* @__PURE__ */ new Set();
442
+ statusQueries = /* @__PURE__ */ new Map();
443
+ audit = [];
444
+ constructor(initial = {}) {
445
+ for (const [userId, balance] of Object.entries(initial)) {
446
+ this.balances.set(userId, balance);
447
+ }
448
+ }
449
+ // ===== 1. MemberAccountPort =====
450
+ async getAccount(userId) {
451
+ return {
452
+ authState: "authenticated",
453
+ state: this.balances.has(userId) ? "active" : "not_enrolled",
454
+ ...this.balances.has(userId) ? { memberRef: `member:${userId}` } : {},
455
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
456
+ };
457
+ }
458
+ // ===== 2. MemberProfilePort =====
459
+ async getProfile(userId) {
460
+ return this.profiles.get(userId) ?? {
461
+ labels: [],
462
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
463
+ };
464
+ }
465
+ // ===== 3. MemberCardPort =====
466
+ async getCard(userId) {
467
+ if (!this.balances.has(userId)) return null;
468
+ return {
469
+ cardRef: `card:${userId}`,
470
+ cardDisplay: "\u793A\u4F8B\u4F1A\u5458\u5361",
471
+ status: "active",
472
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
473
+ };
474
+ }
475
+ // ===== 4. MemberBenefitsPort =====
476
+ async queryPoints(userId) {
477
+ return {
478
+ userId,
479
+ balance: this.balances.get(userId) ?? 0,
480
+ membershipLabel: "\u6D4B\u8BD5\u4F1A\u5458\u5361"
481
+ };
482
+ }
483
+ async getBenefits(userId) {
484
+ return {
485
+ availablePoints: this.balances.get(userId) ?? 0,
486
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
487
+ };
488
+ }
489
+ async getEnrollmentFormDefinition() {
490
+ return {
491
+ formVersion: "fixture-v1",
492
+ fields: [
493
+ { fieldId: "mobile", label: "\u624B\u673A\u53F7", type: "tel", required: true, placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7" },
494
+ { fieldId: "displayName", label: "\u59D3\u540D", type: "text", required: false }
495
+ ],
496
+ consents: [{ consentId: "privacy", version: "1", title: "\u9690\u79C1\u653F\u7B56", required: true }],
497
+ authorizationStatus: "required"
498
+ };
499
+ }
500
+ async listPointUsage(userId, page, pageSize) {
501
+ const all = this.usages.filter((u) => u.usageRecordRef.startsWith(userId));
502
+ const start = (page - 1) * pageSize;
503
+ return { items: all.slice(start, start + pageSize), total: all.length };
504
+ }
505
+ async queryDiscount(userId) {
506
+ void userId;
507
+ return void 0;
508
+ }
509
+ async deductPoints(userId, amount, idempotencyKey) {
510
+ if (this.idempotentKeys.has(idempotencyKey)) return true;
511
+ const current = this.balances.get(userId) ?? 0;
512
+ if (amount <= 0 || current < amount) return false;
513
+ this.balances.set(userId, current - amount);
514
+ this.idempotentKeys.add(idempotencyKey);
515
+ this.audit.push({ action: "deduct", userId, amount, key: idempotencyKey });
516
+ this.usages.push({
517
+ usageRecordRef: `${userId}:${Date.now()}`,
518
+ title: "\u79EF\u5206\u62B5\u6263",
519
+ amount: `-${amount}`,
520
+ usedAt: (/* @__PURE__ */ new Date()).toISOString()
521
+ });
522
+ return true;
523
+ }
524
+ async refundPoints(userId, amount, idempotencyKey) {
525
+ if (this.idempotentKeys.has(`refund:${idempotencyKey}`)) return true;
526
+ const current = this.balances.get(userId) ?? 0;
527
+ this.balances.set(userId, current + amount);
528
+ this.idempotentKeys.add(`refund:${idempotencyKey}`);
529
+ this.audit.push({ action: "refund", userId, amount, key: idempotencyKey });
530
+ this.usages.push({
531
+ usageRecordRef: `${userId}:${Date.now()}`,
532
+ title: "\u79EF\u5206\u9000\u56DE",
533
+ amount: `+${amount}`,
534
+ usedAt: (/* @__PURE__ */ new Date()).toISOString()
535
+ });
536
+ return true;
537
+ }
538
+ async awardPoints(userId, amount) {
539
+ const current = this.balances.get(userId) ?? 0;
540
+ this.balances.set(userId, current + amount);
541
+ this.audit.push({ action: "award", userId, amount });
542
+ this.usages.push({
543
+ usageRecordRef: `${userId}:${Date.now()}`,
544
+ title: "\u6D88\u8D39\u5956\u52B1",
545
+ amount: `+${amount}`,
546
+ usedAt: (/* @__PURE__ */ new Date()).toISOString()
547
+ });
548
+ return true;
549
+ }
550
+ // ===== 5. MemberCouponPort(0.4.0 净删——券域 promotion 承接,恒空面移除) =====
551
+ // ===== 6. MemberEnrollmentPort(015:授权预填 + 幂等 + pending 收口) =====
552
+ async enroll(input) {
553
+ const existing = this.enrollmentAttempts.get(input.idempotencyKey);
554
+ if (existing) return existing.result;
555
+ const mobile = input.fieldValues.mobile ?? "";
556
+ if (!/^1[3-9]\d{9}$/.test(mobile)) {
557
+ return { status: "rejected", reason: "\u624B\u673A\u53F7\u683C\u5F0F\u4E0D\u6B63\u786E" };
558
+ }
559
+ const result = this.balances.has(input.userId) || this.pendingConfirmations.has(input.userId) ? { status: "active", memberRef: `member:${input.userId}` } : { status: "pending_confirmation", attemptRef: input.idempotencyKey };
560
+ if (result.status === "active") {
561
+ this.profiles.set(input.userId, {
562
+ ...input.fieldValues.displayName ? { displayName: input.fieldValues.displayName } : {},
563
+ contactDisplay: mobile.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2"),
564
+ labels: [{ labelRef: "member", name: "\u4F1A\u5458" }],
565
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
566
+ });
567
+ } else {
568
+ this.pendingConfirmations.add(input.userId);
569
+ }
570
+ this.enrollmentAttempts.set(input.idempotencyKey, { result, userId: input.userId });
571
+ return result;
572
+ }
573
+ async getEnrollmentStatus(userId, attemptRef) {
574
+ const attempt = this.enrollmentAttempts.get(attemptRef);
575
+ if (!attempt || attempt.userId !== userId) {
576
+ return { status: "failed", reason: "\u5165\u4F1A\u8BB0\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237" };
577
+ }
578
+ if (this.balances.has(userId)) {
579
+ return { status: "active", memberRef: `member:${userId}` };
580
+ }
581
+ const queried = this.statusQueries.get(attemptRef) ?? 0;
582
+ this.statusQueries.set(attemptRef, queried + 1);
583
+ if (queried >= 1) {
584
+ this.balances.set(userId, 0);
585
+ this.pendingConfirmations.delete(userId);
586
+ return { status: "active", memberRef: `member:${userId}` };
587
+ }
588
+ return { status: "pending_confirmation" };
589
+ }
590
+ };
591
+
592
+ // src/server/ports.ts
593
+ async function resolveMemberSlot(service, reqCtx, ctx) {
594
+ const scope = mallScopeOf(reqCtx);
595
+ if (!scope.mallId) throw new Error("\u4F1A\u5458 C \u7AEF\u8BF7\u6C42\u7F3A\u5C11 mallId");
596
+ if (!ctx.integrations) throw new Error("integrations registry \u7F3A\u5931\uFF1Amember \u670D\u52A1\u4E0D\u53EF\u7528");
597
+ return await ctx.integrations.resolveService({
598
+ service,
599
+ scope,
600
+ request: reqCtx
601
+ });
602
+ }
603
+ async function resolveMemberAccountPort(reqCtx, ctx) {
604
+ return resolveMemberSlot(MEMBER_ACCOUNT_SERVICE, reqCtx, ctx);
605
+ }
606
+ async function resolveMemberProfilePort(reqCtx, ctx) {
607
+ return resolveMemberSlot(MEMBER_PROFILE_SERVICE, reqCtx, ctx);
608
+ }
609
+ async function resolveMemberCardPort(reqCtx, ctx) {
610
+ return resolveMemberSlot(MEMBER_CARD_SERVICE, reqCtx, ctx);
611
+ }
612
+ async function resolveMemberBenefitsPort(reqCtx, ctx) {
613
+ return resolveMemberSlot(MEMBER_BENEFITS_SERVICE, reqCtx, ctx);
614
+ }
615
+ async function resolveMemberEnrollmentPort(reqCtx, ctx) {
616
+ return resolveMemberSlot(MEMBER_ENROLLMENT_SERVICE, reqCtx, ctx);
617
+ }
618
+
619
+ // src/server/status.ts
620
+ var MEMBER_LOGIN_NOTICE = "\u8BF7\u5148\u767B\u5F55\uFF1B\u767B\u5F55\u4E0D\u8981\u6C42\u5F00\u901A\u4F1A\u5458";
621
+ var MEMBER_UNKNOWN_NOTICE = "\u5F53\u524D\u65E0\u6CD5\u786E\u8BA4\u4F1A\u5458\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
622
+ var MEMBER_NOT_ENROLLED_NOTICE = "\u5C1A\u672A\u5165\u4F1A\uFF0C\u5165\u4F1A\u540E\u53EF\u67E5\u8BE2\u79EF\u5206\u4E0E\u670D\u52A1";
623
+ var MEMBER_NO_POINTS_NOTICE = "\u5F53\u524D\u6CA1\u6709\u53EF\u67E5\u8BE2\u7684\u4F1A\u5458\u79EF\u5206";
624
+ var MEMBER_QUERY_NOTICES = {
625
+ unauthenticated: "\u8BF7\u5148\u767B\u5F55\u540E\u518D\u67E5\u8BE2\u4F1A\u5458\u4FE1\u606F",
626
+ unknown: MEMBER_UNKNOWN_NOTICE,
627
+ not_enrolled: MEMBER_NOT_ENROLLED_NOTICE,
628
+ closed: MEMBER_NO_POINTS_NOTICE,
629
+ unavailable: "\u4F1A\u5458\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5"
630
+ };
631
+ async function resolveMemberStatus(reqCtx, ctx) {
632
+ if (!reqCtx) {
633
+ ctx.logger?.warn("\u4F1A\u5458\u72B6\u6001\u89E3\u6790\u5931\u8D25\uFF1A\u8FD0\u884C\u4E0A\u4E0B\u6587\u7F3A\u5E2D");
634
+ return { outcome: "unavailable" };
635
+ }
636
+ if (reqCtx.identity?.source === "anonymous") {
637
+ let loginUrl;
638
+ try {
639
+ const accountPort2 = await resolveMemberAccountPort(reqCtx, ctx);
640
+ loginUrl = accountPort2.loginUrl || void 0;
641
+ } catch {
642
+ }
643
+ return loginUrl ? { outcome: "unauthenticated", loginUrl } : { outcome: "unauthenticated" };
644
+ }
645
+ let accountPort;
646
+ let account;
647
+ try {
648
+ accountPort = await resolveMemberAccountPort(reqCtx, ctx);
649
+ account = await accountPort.getAccount(reqCtx.identity.userId);
650
+ } catch (error) {
651
+ ctx.logger?.warn(`\u4F1A\u5458\u72B6\u6001\u89E3\u6790\u5931\u8D25\uFF1Aerr=${error.message}`);
652
+ return { outcome: "unavailable" };
653
+ }
654
+ let enrollUrl;
655
+ try {
656
+ const enrollmentPort = await resolveMemberEnrollmentPort(reqCtx, ctx);
657
+ enrollUrl = enrollmentPort.enrollUrl || void 0;
658
+ } catch (error) {
659
+ ctx.logger?.warn(`\u4F1A\u5458\u5165\u4F1A\u6DF1\u94FE\u89E3\u6790\u5931\u8D25\uFF1Aerr=${error.message}`);
660
+ }
661
+ return {
662
+ outcome: classifyMemberOutcome(reqCtx.identity, account),
663
+ account,
664
+ ...enrollUrl ? { enrollUrl } : {},
665
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
666
+ };
667
+ }
668
+
669
+ // src/server/tool.ts
670
+ import { z } from "zod";
671
+ function withCards(facts, cardType, note) {
672
+ return { ...facts, cards: [{ cardType, data: facts, note }] };
673
+ }
674
+ function memberStateLabel(state) {
675
+ switch (state) {
676
+ case "active":
677
+ return "\u5DF2\u5165\u4F1A";
678
+ case "not_enrolled":
679
+ return "\u672A\u5165\u4F1A";
680
+ case "closed":
681
+ return "\u5DF2\u9000\u4F1A";
682
+ case "unauthenticated":
683
+ return "\u672A\u767B\u5F55";
684
+ default:
685
+ return "\u72B6\u6001\u786E\u8BA4\u4E2D";
686
+ }
687
+ }
688
+ var MEMBER_CONTRACT_NOTICE = "\u4F1A\u5458\u5361\u540D\u4E0D\u662F\u4F1A\u5458\u7B49\u7EA7\uFF1B\u5F53\u524D\u67E5\u8BE2\u672A\u8FD4\u56DE\u4F1A\u5458\u6743\u76CA\uFF0C\u4E0D\u5F97\u63A8\u65AD\u6298\u6263\u3001\u505C\u8F66\u3001\u751F\u65E5\u6216\u6D3B\u52A8\u6743\u76CA";
689
+ var MEMBER_PRIVACY_REFUSAL = "\u51FA\u4E8E\u4F1A\u5458\u9690\u79C1\u4FDD\u62A4\uFF0C\u6211\u53EA\u80FD\u67E5\u8BE2\u5F53\u524D\u767B\u5F55\u4F1A\u5458\u7684\u4FE1\u606F\uFF0C\u4E0D\u80FD\u4FEE\u6539\u4F1A\u5458\u8D44\u6599\u3001\u4EE3\u67E5\u4ED6\u4EBA\u8D26\u6237\uFF0C\u4E5F\u4E0D\u80FD\u901A\u8FC7\u624B\u673A\u53F7\u6216\u4F1A\u5458\u5361\u53F7\u5207\u6362\u8D26\u6237\u3002";
690
+ function memberPrivacyRefusal(query) {
691
+ if (typeof query !== "string" || query.trim().length === 0) return void 0;
692
+ const content = query.replace(/\s+/g, "");
693
+ const profileMutation = /(手机号|手机号码|联系方式).{0,12}(改|修改|更换|变更|替换|更新)|(改|修改|更换|变更|替换|更新).{0,12}(手机号|手机号码|联系方式)/.test(content);
694
+ const thirdPartyLookup = /(老婆|老公|妻子|丈夫|他人|别人|朋友|同事|家人|孩子|女儿|儿子|妈妈|爸爸).{0,20}(手机号|手机号码|会员|积分|卡)/.test(content);
695
+ const suppliedIdentifierLookup = /(用|通过|根据|拿着|拿).{0,20}(手机号|手机号码|会员卡号|卡号).{0,20}(查|查询|切|切换|登录|绑定)/.test(content) || /(切换|切到|换到|登录到|绑定到).{0,15}(账号|账户|会员卡|卡号|手机号|手机号码)/.test(content);
696
+ const explicitIdentifier = /(手机号|手机号码|会员卡号|卡号|账号|账户).{0,16}\d{5,}/.test(content);
697
+ return profileMutation || thirdPartyLookup || suppliedIdentifierLookup || explicitIdentifier ? MEMBER_PRIVACY_REFUSAL : void 0;
698
+ }
699
+ function hasCompleteMemberCard(card) {
700
+ return card?.presentation !== void 0;
701
+ }
702
+ function cardContractNotice(card) {
703
+ return card.benefits && card.benefits.length > 0 ? "\u4F1A\u5458\u5361\u540D\u4E0D\u662F\u4F1A\u5458\u7B49\u7EA7\uFF1B\u4EC5\u5C55\u793A\u7CFB\u7EDF\u8FD4\u56DE\u7684\u5F53\u524D\u7B49\u7EA7\u6743\u76CA\uFF0C\u4E0D\u5F97\u63A8\u65AD\u989D\u5916\u6298\u6263\u6216\u670D\u52A1" : MEMBER_CONTRACT_NOTICE;
704
+ }
705
+ function buildMemberCardFacts(userId, card, availablePoints, includePreferencePrompts = false) {
706
+ return {
707
+ userId,
708
+ cardRef: card.cardRef,
709
+ cardDisplay: card.cardDisplay,
710
+ status: card.status,
711
+ ...card.levelName ? { levelName: card.levelName } : {},
712
+ availablePoints,
713
+ ...card.cardImageUrl ? { cardImageUrl: card.cardImageUrl } : {},
714
+ ...card.pointsRuleImageUrl ? { pointsRuleImageUrl: card.pointsRuleImageUrl } : {},
715
+ ...card.pointsRuleContent ? { pointsRuleContent: card.pointsRuleContent } : {},
716
+ ...card.pointsUrl ? { pointsUrl: card.pointsUrl } : {},
717
+ ...card.presentation ? { presentation: card.presentation } : {},
718
+ ...card.progress ? { progress: card.progress } : {},
719
+ ...card.benefits ? { benefits: [...card.benefits] } : {},
720
+ ...includePreferencePrompts ? { preferencePrompts: [...MEMBER_PREFERENCE_PROMPTS] } : {},
721
+ contractNotice: cardContractNotice(card)
722
+ };
723
+ }
724
+ function memberCardCandidate(facts) {
725
+ return {
726
+ cardType: "member-card-summary",
727
+ data: facts,
728
+ note: `\u4F1A\u5458\u5361\uFF08\u53EF\u7528\u79EF\u5206 ${facts.availablePoints}\uFF09`
729
+ };
730
+ }
731
+ function anonymousShort(userId, action) {
732
+ return {
733
+ userId,
734
+ requiresAuth: true,
735
+ found: false,
736
+ noResultsReason: `\u8BF7\u5148\u767B\u5F55\u540E\u518D${action}`
737
+ };
738
+ }
739
+ async function maybeEnrollmentCta(reqCtx, account, links) {
740
+ const outcome = classifyMemberOutcome2(reqCtx.identity, account);
741
+ if (outcome !== "not_enrolled" && outcome !== "closed") return void 0;
742
+ return memberGateCardOf(reqCtx.identity.userId, {
743
+ outcome,
744
+ ...links.enrollUrl ? { enrollUrl: links.enrollUrl } : {},
745
+ ...links.loginUrl ? { loginUrl: links.loginUrl } : {}
746
+ });
747
+ }
748
+ function queryNoResultsNotice(account) {
749
+ if (account.authState === "unauthenticated") return MEMBER_LOGIN_NOTICE;
750
+ if (account.state === "not_enrolled") return MEMBER_NOT_ENROLLED_NOTICE;
751
+ if (account.state === "closed") return MEMBER_NO_POINTS_NOTICE;
752
+ return MEMBER_UNKNOWN_NOTICE;
753
+ }
754
+ function queryNonActiveNotice(account) {
755
+ return account.state === "closed" ? MEMBER_NO_POINTS_NOTICE : MEMBER_NOT_ENROLLED_NOTICE;
756
+ }
757
+ function createQueryPointsTool(ports) {
758
+ return createTool({
759
+ id: "queryPoints",
760
+ description: "\u67E5\u8BE2\u4F1A\u5458\u5F53\u524D\u79EF\u5206\u4F59\u989D\u3001\u4F1A\u5458\u5361\u9762\u4E0E\u79EF\u5206\u6D41\u6C34\uFF1B\u8FD4\u56DE\u7684 balance \u662F\u5F53\u524D\u79EF\u5206\u70B9\u6570\uFF0Citems \u4E2D title \u662F\u6D41\u6C34\u4E1A\u52A1\u8BF4\u660E\u3001amount \u662F\u672C\u7B14\u53D8\u52A8\uFF08\u6B63\u6570\u589E\u52A0\u3001\u8D1F\u6570\u6263\u51CF\uFF09\u3001usedAt \u662F\u53D1\u751F\u65F6\u95F4\u3001description \u662F\u53EF\u9009\u95E8\u5E97\u540D\u79F0\uFF1B\u6D41\u6C34\u4EC5\u4F9B\u6A21\u578B\u7EC4\u7EC7\u6587\u672C\uFF0C\u4E0D\u6784\u9020\u79EF\u5206\u6D41\u6C34\u5361\u3002\u5DF2\u767B\u5F55\u672A\u5165\u4F1A\u65F6\u8FD4\u56DE\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08member-enrollment-cta\uFF09\uFF0C\u65E0\u9700\u518D\u8C03 queryEnrollmentCta\u3002",
761
+ inputSchema: z.object({
762
+ query: z.string().optional().describe("\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\uFF1B\u7528\u4E8E\u4FDD\u7559\u67E5\u8BE2\u8BED\u5883\uFF0C\u4E0D\u53C2\u4E0E\u8EAB\u4EFD\u6216\u8D44\u6E90\u5B9A\u4F4D")
763
+ }),
764
+ execute: async (input, execCtx) => {
765
+ const reqCtx = requireToolRequestContext(execCtx, "queryPoints");
766
+ const privacyRefusal = memberPrivacyRefusal(input.query);
767
+ if (privacyRefusal) return { found: false, noResultsReason: privacyRefusal };
768
+ const userId = reqCtx.identity.userId;
769
+ if (reqCtx.identity.source === "anonymous") {
770
+ return anonymousShort(userId, "\u67E5\u8BE2\u4F1A\u5458\u79EF\u5206");
771
+ }
772
+ const [accountPort, cardPort, benefitsPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getCard(reqCtx), ports.getBenefits(reqCtx), ports.getEnrollment(reqCtx)]);
773
+ const account = await accountPort.getAccount(userId);
774
+ if (account.authState === "unauthenticated" || account.state !== "active") {
775
+ const enrollCard = await maybeEnrollmentCta(reqCtx, account, { enrollUrl: enrollmentPort.enrollUrl, loginUrl: accountPort.loginUrl });
776
+ if (enrollCard) {
777
+ return {
778
+ userId,
779
+ found: false,
780
+ authState: account.authState,
781
+ state: account.state,
782
+ noResultsReason: queryNonActiveNotice(account),
783
+ cards: [enrollCard]
784
+ };
785
+ }
786
+ return {
787
+ userId,
788
+ found: false,
789
+ authState: account.authState,
790
+ state: account.state,
791
+ noResultsReason: queryNoResultsNotice(account)
792
+ };
793
+ }
794
+ const card = await cardPort.getCard(userId);
795
+ let points = { userId, balance: 0 };
796
+ let pointsUnavailable = false;
797
+ try {
798
+ points = await benefitsPort.queryPoints(userId);
799
+ } catch {
800
+ pointsUnavailable = true;
801
+ }
802
+ const completeCard = hasCompleteMemberCard(card);
803
+ let usage = { items: [], total: 0 };
804
+ let usageUnavailable = false;
805
+ if (!completeCard || card?.pointsUrl) {
806
+ try {
807
+ usage = await benefitsPort.listPointUsage(userId, 1, 10);
808
+ } catch {
809
+ usageUnavailable = true;
810
+ }
811
+ }
812
+ const facts = {
813
+ userId,
814
+ balance: points.balance,
815
+ ...points.membershipLabel ? { membershipLabel: points.membershipLabel } : {},
816
+ items: [...usage.items],
817
+ total: usage.total,
818
+ contractNotice: MEMBER_CONTRACT_NOTICE,
819
+ ...pointsUnavailable ? { pointsNotice: "\u5F53\u524D\u79EF\u5206\u4F59\u989D\u6682\u4E0D\u53EF\u7528\uFF0C\u4F1A\u5458\u5361\u4ECD\u53EF\u6B63\u5E38\u67E5\u770B" } : {},
820
+ ...usageUnavailable ? { pointsHistoryNotice: "\u6682\u65E0\u6700\u8FD1\u79EF\u5206\u6D41\u6C34\u8BB0\u5F55" } : {}
821
+ };
822
+ if (!card) return { ...facts, cards: [] };
823
+ const memberCard = memberCardCandidate(buildMemberCardFacts(userId, card, points.balance, true));
824
+ return { ...facts, cards: [memberCard] };
825
+ }
826
+ });
827
+ }
828
+ function createQueryMemberInfoTool(ports) {
829
+ return createTool({
830
+ id: "queryMemberInfo",
831
+ description: "\u67E5\u8BE2\u660E\u786E\u7684\u4F1A\u5458\u8D26\u6237\u8D44\u6599\uFF08\u662F\u5426\u5165\u4F1A\u3001\u6635\u79F0\u3001\u8054\u7CFB\u65B9\u5F0F\u3001\u6807\u7B7E\uFF09\uFF1B\u4F20\u5165\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\u4F5C\u4E3A query\uFF08\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D\uFF09\uFF1B\u5DF2\u767B\u5F55\u672A\u5165\u4F1A\u65F6\u9644\u5E26\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08member-enrollment-cta\uFF09\uFF0C\u65E0\u9700\u518D\u8C03 queryEnrollmentCta\u3002",
832
+ inputSchema: z.object({ query: z.string().optional().describe("\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\uFF0C\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D") }),
833
+ execute: async (input, execCtx) => {
834
+ const reqCtx = requireToolRequestContext(execCtx, "queryMemberInfo");
835
+ const privacyRefusal = memberPrivacyRefusal(input.query);
836
+ if (privacyRefusal) return { found: false, noResultsReason: privacyRefusal };
837
+ const userId = reqCtx.identity.userId;
838
+ if (reqCtx.identity.source === "anonymous") {
839
+ return anonymousShort(userId, "\u67E5\u8BE2\u4F1A\u5458\u4FE1\u606F");
840
+ }
841
+ const [accountPort, cardPort, benefitsPort, profilePort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getCard(reqCtx), ports.getBenefits(reqCtx), ports.getProfile(reqCtx), ports.getEnrollment(reqCtx)]);
842
+ const account = await accountPort.getAccount(userId);
843
+ const profile = await profilePort.getProfile(userId);
844
+ const facts = {
845
+ userId,
846
+ authState: account.authState,
847
+ state: account.state,
848
+ ...profile.displayName ? { displayName: profile.displayName } : {},
849
+ ...profile.avatarUrl ? { avatarUrl: profile.avatarUrl } : {},
850
+ ...profile.contactDisplay ? { contactDisplay: profile.contactDisplay } : {},
851
+ ...account.memberRef ? { memberRef: account.memberRef } : {},
852
+ ...account.registeredAt ? { registeredAt: account.registeredAt } : {},
853
+ labels: profile.labels ?? [],
854
+ contractNotice: MEMBER_CONTRACT_NOTICE
855
+ };
856
+ const infoCard = {
857
+ cardType: "member-info",
858
+ data: facts,
859
+ // note 契约(W7):账户态中文化 + 姓名要点
860
+ note: `\u4F1A\u5458\u8D26\u6237\uFF08${memberStateLabel(facts.state)}\uFF09${facts.displayName ? ` \xB7 ${facts.displayName}` : ""}`
861
+ };
862
+ if (account.state !== "active") {
863
+ const enrollCard = await maybeEnrollmentCta(reqCtx, account, { enrollUrl: enrollmentPort.enrollUrl, loginUrl: accountPort.loginUrl });
864
+ if (enrollCard) return { ...facts, cards: [enrollCard, infoCard] };
865
+ return { ...facts, cards: [infoCard] };
866
+ }
867
+ const [card, points] = await Promise.all([
868
+ cardPort.getCard(userId),
869
+ benefitsPort.queryPoints(userId)
870
+ ]);
871
+ if (!card) return { ...facts, cards: [infoCard] };
872
+ const memberCard = memberCardCandidate(buildMemberCardFacts(userId, card, points.balance));
873
+ if (hasCompleteMemberCard(card)) return { ...facts, cards: [memberCard] };
874
+ return {
875
+ ...facts,
876
+ cards: [memberCard, infoCard]
877
+ };
878
+ }
879
+ });
880
+ }
881
+ function createQueryMemberCardTool(ports) {
882
+ return createTool({
883
+ id: "queryMemberCard",
884
+ description: "\u67E5\u8BE2\u4F1A\u5458\u5361\u4FE1\u606F\uFF08\u5361\u9762\u3001\u5361\u540D\u3001\u79EF\u5206\u6458\u8981\uFF09\uFF1B\u4F20\u5165\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\u4F5C\u4E3A query\uFF08\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D\uFF09\uFF1B\u5DF2\u767B\u5F55\u672A\u5165\u4F1A\u65F6\u8FD4\u56DE\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08member-enrollment-cta\uFF09\uFF0C\u65E0\u9700\u518D\u8C03 queryEnrollmentCta\u3002",
885
+ inputSchema: z.object({ query: z.string().optional().describe("\u5F53\u524D\u7528\u6237\u539F\u59CB\u95EE\u9898\uFF0C\u4E0D\u7528\u4E8E\u8EAB\u4EFD\u5B9A\u4F4D") }),
886
+ execute: async (input, execCtx) => {
887
+ const reqCtx = requireToolRequestContext(execCtx, "queryMemberCard");
888
+ const privacyRefusal = memberPrivacyRefusal(input.query);
889
+ if (privacyRefusal) return { found: false, noResultsReason: privacyRefusal };
890
+ const userId = reqCtx.identity.userId;
891
+ if (reqCtx.identity.source === "anonymous") {
892
+ return anonymousShort(userId, "\u67E5\u8BE2\u4F1A\u5458\u5361");
893
+ }
894
+ const [accountPort, cardPort, benefitsPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getCard(reqCtx), ports.getBenefits(reqCtx), ports.getEnrollment(reqCtx)]);
895
+ const account = await accountPort.getAccount(userId);
896
+ if (account.authState === "unauthenticated" || account.state === "unknown") {
897
+ return {
898
+ userId,
899
+ found: false,
900
+ authState: account.authState,
901
+ state: account.state,
902
+ noResultsReason: account.authState === "unauthenticated" ? MEMBER_LOGIN_NOTICE : MEMBER_UNKNOWN_NOTICE
903
+ };
904
+ }
905
+ if (account.state !== "active") {
906
+ const enrollCard = await maybeEnrollmentCta(reqCtx, account, { enrollUrl: enrollmentPort.enrollUrl, loginUrl: accountPort.loginUrl });
907
+ if (enrollCard) {
908
+ return {
909
+ userId,
910
+ found: false,
911
+ authState: account.authState,
912
+ state: account.state,
913
+ noResultsReason: queryNonActiveNotice(account),
914
+ cards: [enrollCard]
915
+ };
916
+ }
917
+ return {
918
+ userId,
919
+ found: false,
920
+ authState: account.authState,
921
+ state: account.state,
922
+ noResultsReason: "\u5F53\u524D\u7528\u6237\u5C1A\u672A\u5728\u8BE5\u5546\u573A\u5F00\u901A\u4F1A\u5458\u5361"
923
+ };
924
+ }
925
+ const card = await cardPort.getCard(userId);
926
+ const points = await benefitsPort.queryPoints(userId);
927
+ if (!card) {
928
+ return {
929
+ userId,
930
+ found: false,
931
+ authState: account.authState,
932
+ state: "unknown",
933
+ noResultsReason: "\u4F1A\u5458\u8D26\u6237\u5DF2\u6FC0\u6D3B\uFF0C\u4F46\u6682\u672A\u53D6\u5F97\u4F1A\u5458\u5361\u4FE1\u606F\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
934
+ };
935
+ }
936
+ const facts = buildMemberCardFacts(userId, card, points.balance);
937
+ return withCards(facts, "member-card-summary", `\u4F1A\u5458\u5361\uFF08\u53EF\u7528\u79EF\u5206 ${points.balance}\uFF09`);
938
+ }
939
+ });
940
+ }
941
+ function createQueryEnrollmentCtaTool(ports) {
942
+ return createTool({
943
+ id: "queryEnrollmentCta",
944
+ description: "\u5F00\u5361\u5165\u4F1A\u5F15\u5BFC\uFF1A\u5F53\u7528\u6237\u8BE2\u95EE\u5982\u4F55\u6210\u4E3A\u4F1A\u5458\u3001\u600E\u4E48\u5F00\u5361\u3001\u5165\u4F1A\u6D41\u7A0B\u3001\u4F1A\u5458\u6CE8\u518C\u65F6\u8C03\u7528\uFF0C\u8FD4\u56DE\u5F00\u5361\u5165\u4F1A\u5F15\u5BFC\u5361\uFF08\u70B9\u51FB\u540E\u8DF3\u8F6C\u652F\u4ED8\u5B9D\u5C0F\u7A0B\u5E8F\u5B8C\u6210\u5165\u4F1A\uFF09\u3002\u3010\u6CE8\u610F\u3011\u5DF2\u5165\u4F1A\u7528\u6237\u4E0D\u51FA\u6B64\u5361\uFF0C\u6539\u4E3A\u67E5\u8BE2 queryMemberCard\u3002",
945
+ inputSchema: z.object({}),
946
+ execute: async (_input, execCtx) => {
947
+ const reqCtx = requireToolRequestContext(execCtx, "queryEnrollmentCta");
948
+ const userId = reqCtx.identity.userId;
949
+ const [accountPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getEnrollment(reqCtx)]);
950
+ const account = await accountPort.getAccount(userId);
951
+ if (account.state === "active") {
952
+ return {
953
+ userId,
954
+ found: false,
955
+ noResultsReason: "\u60A8\u5DF2\u662F\u4F1A\u5458\uFF0C\u65E0\u9700\u91CD\u590D\u5F00\u5361",
956
+ authState: account.authState,
957
+ state: "active"
958
+ };
959
+ }
960
+ if (account.authState === "unauthenticated") {
961
+ const loginFacts = {
962
+ title: "\u767B\u5F55\u540E\u7EE7\u7EED",
963
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
964
+ };
965
+ return {
966
+ ...loginFacts,
967
+ found: true,
968
+ cards: [{ cardType: "member-login-cta", data: loginFacts, note: "\u672A\u767B\u5F55 \xB7 \u70B9\u51FB\u5361\u7247\u767B\u5F55" }]
969
+ };
970
+ }
971
+ if (account.state === "unknown") {
972
+ return {
973
+ userId,
974
+ found: false,
975
+ authState: account.authState,
976
+ state: account.state,
977
+ noResultsReason: "\u5F53\u524D\u65E0\u6CD5\u786E\u8BA4\u4F1A\u5458\u72B6\u6001\uFF0C\u8BF7\u5237\u65B0\u767B\u5F55\u72B6\u6001\u540E\u91CD\u8BD5"
978
+ };
979
+ }
980
+ const status = {
981
+ outcome: account.state,
982
+ ...enrollmentPort.enrollUrl ? { enrollUrl: enrollmentPort.enrollUrl } : {},
983
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
984
+ };
985
+ const card = memberGateCardOf(reqCtx.identity.userId, status);
986
+ if (!card) {
987
+ return {
988
+ userId,
989
+ found: false,
990
+ authState: account.authState,
991
+ state: account.state,
992
+ noResultsReason: "\u5F53\u524D\u65E0\u6CD5\u786E\u8BA4\u4F1A\u5458\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
993
+ };
994
+ }
995
+ const facts = { userId, ...card.data };
996
+ return withCards(facts, card.cardType, card.note);
997
+ }
998
+ });
999
+ }
1000
+ function createQueryEnrollmentFormTool(ports) {
1001
+ return createTool({
1002
+ id: "queryEnrollmentForm",
1003
+ description: "\u67E5\u8BE2\u4F1A\u5458\u5165\u4F1A\u72B6\u6001\u5E76\u8FD4\u56DE\u5165\u4F1A\u8868\u5355\uFF08\u542B\u652F\u4ED8\u5B9D\u6388\u6743\u9884\u586B\u5165\u53E3\u3001\u9690\u79C1\u540C\u610F\u9879\uFF1B\u672A\u5165\u4F1A\u65F6\u7528\u6237\u53EF\u6388\u6743\u6216\u624B\u52A8\u586B\u5199\u540E\u63D0\u4EA4\uFF09",
1004
+ inputSchema: z.object({}),
1005
+ execute: async (_input, execCtx) => {
1006
+ const reqCtx = requireToolRequestContext(execCtx, "queryEnrollmentForm");
1007
+ const userId = reqCtx.identity.userId;
1008
+ if (reqCtx.identity.source === "anonymous") {
1009
+ return anonymousShort(userId, "\u67E5\u8BE2\u5165\u4F1A\u8868\u5355");
1010
+ }
1011
+ const [accountPort, enrollmentPort] = await Promise.all([ports.getAccount(reqCtx), ports.getEnrollment(reqCtx)]);
1012
+ const account = await accountPort.getAccount(userId);
1013
+ const notEnrolled = account.authState === "authenticated" && (account.state === "not_enrolled" || account.state === "closed");
1014
+ const definition = notEnrolled && typeof enrollmentPort.getEnrollmentFormDefinition === "function" ? await enrollmentPort.getEnrollmentFormDefinition(userId) : void 0;
1015
+ if (!notEnrolled) {
1016
+ return {
1017
+ userId,
1018
+ authState: account.authState,
1019
+ state: account.state,
1020
+ found: false,
1021
+ noResultsReason: account.authState === "unauthenticated" ? MEMBER_LOGIN_NOTICE : account.state === "active" ? "\u5F53\u524D\u7528\u6237\u5DF2\u662F\u4F1A\u5458\uFF0C\u65E0\u9700\u91CD\u590D\u5F00\u5361" : MEMBER_UNKNOWN_NOTICE
1022
+ };
1023
+ }
1024
+ const facts = {
1025
+ userId,
1026
+ state: account.state,
1027
+ formVersion: definition?.formVersion ?? "unknown",
1028
+ fields: definition ? [...definition.fields] : [],
1029
+ consents: definition ? [...definition.consents] : [],
1030
+ authorization: { status: definition?.authorizationStatus ?? "unavailable" }
1031
+ };
1032
+ return withCards(facts, "member-enrollment-form", "\u672A\u5165\u4F1A \xB7 \u586B\u5199\u8868\u5355\u5B8C\u6210\u5165\u4F1A");
1033
+ }
1034
+ });
1035
+ }
1036
+ function createQueryLoginCtaTool(ports) {
1037
+ return createTool({
1038
+ id: "queryLoginCta",
1039
+ description: "\u767B\u5F55\u5F15\u5BFC\uFF1A\u5F53\u7528\u6237\u8868\u793A\u8981\u767B\u5F55\u3001\u8BF4\u81EA\u5DF1\u672A\u767B\u5F55\uFF0C\u6216\u5176\u5B83\u80FD\u529B\u56E0\u7F3A\u5C11\u767B\u5F55\u8EAB\u4EFD\u65E0\u6CD5\u7EE7\u7EED\u65F6\u8C03\u7528\uFF0C\u8FD4\u56DE\u767B\u5F55\u5F15\u5BFC\u5361\uFF08\u70B9\u51FB\u540E\u8DF3\u8F6C\u5546\u573A\u5C0F\u7A0B\u5E8F\u767B\u5F55\u9875\uFF0C\u5B8C\u6210\u540E\u56DE\u5230\u5BF9\u8BDD\uFF09\u3002",
1040
+ inputSchema: z.object({}),
1041
+ execute: async (_input, execCtx) => {
1042
+ const reqCtx = requireToolRequestContext(execCtx, "queryLoginCta");
1043
+ const accountPort = await ports.getAccount(reqCtx);
1044
+ const loginFacts = {
1045
+ title: "\u767B\u5F55\u540E\u7EE7\u7EED",
1046
+ ...accountPort.loginUrl ? { loginUrl: accountPort.loginUrl } : {}
1047
+ };
1048
+ return withCards(loginFacts, "member-login-cta", "\u672A\u767B\u5F55 \xB7 \u70B9\u51FB\u5361\u7247\u767B\u5F55");
1049
+ }
1050
+ });
1051
+ }
1052
+
1053
+ // src/server/handler.ts
1054
+ import {
1055
+ MEMBER_PREFERENCE_PROMPTS as MEMBER_PREFERENCE_PROMPTS2,
1056
+ MEMBER_PREFERENCE_RECOMMENDATION_QUERIES,
1057
+ MEMBER_PREFERENCE_VALUES,
1058
+ parseMallScope
1059
+ } from "@tbox.cn/app-contracts-mall";
1060
+ function matchMemberPreference(content) {
1061
+ return MEMBER_PREFERENCE_PROMPTS2.find((prompt) => content === prompt.query);
1062
+ }
1063
+ var MEMBER_PREFERENCE_ACK_TEXTS = {
1064
+ "\u6211\u5E38\u6765\u4E70\u8863\u670D": "\u4F60\u5E38\u6765\u4E70\u8863\u670D",
1065
+ "\u6211\u5E38\u5E26\u5A03\u6765\u73A9": "\u4F60\u5E38\u5E26\u5A03\u6765\u73A9",
1066
+ "\u6211\u5E38\u6765\u5403\u7F8E\u98DF": "\u4F60\u5E38\u6765\u5403\u7F8E\u98DF"
1067
+ };
1068
+ function createMemberHandler(ctx) {
1069
+ return {
1070
+ id: "member",
1071
+ // match:'exact':偏好按钮发出的是固定整句值(member-card-summary 按钮组),
1072
+ // 整句全等命中——不入 BM25 词袋(相似查询「2楼有啥美食可逛」曾被词袋误吞,F3)
1073
+ intents: [{ patterns: [...MEMBER_PREFERENCE_VALUES], match: "exact" }],
1074
+ handle: async (action, payload, _ctx, reqCtx) => {
1075
+ if (action === "intent") {
1076
+ const content = typeof payload?.content === "string" ? payload.content.trim() : "";
1077
+ const prompt = matchMemberPreference(content);
1078
+ if (!prompt) return { cards: [], continueWith: { content } };
1079
+ const identity = reqCtx?.identity;
1080
+ if (!identity || identity.source === "anonymous") {
1081
+ return { cards: [], text: "\u767B\u5F55\u540E\u624D\u80FD\u4FDD\u5B58\u4F1A\u5458\u504F\u597D" };
1082
+ }
1083
+ if (!ctx.memory || ctx.memory.capabilities?.save === false) {
1084
+ return { cards: [], text: "\u504F\u597D\u8BB0\u5FC6\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5" };
1085
+ }
1086
+ const mallId = parseMallScope(reqCtx?.attributes)?.mallId;
1087
+ await ctx.memory.save({
1088
+ userId: identity.userId,
1089
+ ...mallId ? { scope: { mallId } } : {},
1090
+ kind: "preference",
1091
+ content: `\u4F1A\u5458\u504F\u597D\uFF1A${prompt.value}`
1092
+ });
1093
+ return {
1094
+ cards: [],
1095
+ text: `\u8BB0\u4F4F\u5566\uFF0C${MEMBER_PREFERENCE_ACK_TEXTS[prompt.value]}\u3002\u6B63\u5728\u4E3A\u4F60\u67E5\u627E\u76F8\u5173\u7684\u95E8\u5E97\u548C\u5BFC\u8D2D\u670D\u52A1\uFF0C\u8BF7\u7A0D\u7B49\u3002
1096
+
1097
+ `,
1098
+ continueWith: { content: MEMBER_PREFERENCE_RECOMMENDATION_QUERIES[prompt.value] }
1099
+ };
1100
+ }
1101
+ if (!reqCtx) return { cards: [], text: "\u4F1A\u5458\u670D\u52A1\u4E0D\u53EF\u7528\uFF08\u8FD0\u884C\u4E0A\u4E0B\u6587\u7F3A\u5931\uFF09" };
1102
+ const query = typeof payload?.query === "string" ? payload.query : "";
1103
+ const privacyRefusal = memberPrivacyRefusal(query);
1104
+ if (privacyRefusal) return { cards: [], text: privacyRefusal };
1105
+ const status = await resolveMemberStatus(reqCtx, ctx);
1106
+ if (status.outcome === "active") {
1107
+ const userId = reqCtx.identity.userId;
1108
+ switch (action) {
1109
+ case "query-points": {
1110
+ let cardPort;
1111
+ let benefitsPort;
1112
+ try {
1113
+ [cardPort, benefitsPort] = await Promise.all([
1114
+ resolveMemberCardPort(reqCtx, ctx),
1115
+ resolveMemberBenefitsPort(reqCtx, ctx)
1116
+ ]);
1117
+ } catch (error) {
1118
+ ctx.logger?.warn(`member handler\uFF1Aactive \u69FD\u89E3\u6790\u5931\u8D25\uFF1Aerr=${error.message}`);
1119
+ return { cards: [], text: MEMBER_QUERY_NOTICES.unavailable };
1120
+ }
1121
+ const card = await cardPort.getCard(userId);
1122
+ let points = { userId, balance: 0 };
1123
+ let pointsUnavailable = false;
1124
+ try {
1125
+ points = await benefitsPort.queryPoints(userId);
1126
+ } catch {
1127
+ pointsUnavailable = true;
1128
+ }
1129
+ if (card?.presentation !== void 0) {
1130
+ let usage2;
1131
+ let usageUnavailable = false;
1132
+ if (card.pointsUrl) {
1133
+ try {
1134
+ usage2 = await benefitsPort.listPointUsage(userId, 1, 10);
1135
+ } catch {
1136
+ usageUnavailable = true;
1137
+ }
1138
+ }
1139
+ const cardData = {
1140
+ userId,
1141
+ cardRef: card.cardRef,
1142
+ cardDisplay: card.cardDisplay,
1143
+ status: card.status,
1144
+ ...card.levelName ? { levelName: card.levelName } : {},
1145
+ availablePoints: points.balance,
1146
+ ...card.cardImageUrl ? { cardImageUrl: card.cardImageUrl } : {},
1147
+ ...card.pointsRuleImageUrl ? { pointsRuleImageUrl: card.pointsRuleImageUrl } : {},
1148
+ ...card.pointsRuleContent ? { pointsRuleContent: card.pointsRuleContent } : {},
1149
+ ...card.pointsUrl ? { pointsUrl: card.pointsUrl } : {},
1150
+ ...card.presentation ? { presentation: card.presentation } : {},
1151
+ ...card.progress ? { progress: card.progress } : {},
1152
+ ...card.benefits ? { benefits: [...card.benefits] } : {}
1153
+ };
1154
+ return {
1155
+ cards: [{ cardType: "member-card-summary", data: cardData }],
1156
+ text: pointsUnavailable ? "\u4F1A\u5458\u5361\u53EF\u6B63\u5E38\u67E5\u770B\uFF0C\u4F46\u5F53\u524D\u79EF\u5206\u4F59\u989D\u6682\u4E0D\u53EF\u7528\uFF0C\u6682\u65E0\u6700\u8FD1\u79EF\u5206\u6D41\u6C34\u8BB0\u5F55" : usageUnavailable ? `\u5F53\u524D\u79EF\u5206 ${points.balance}\uFF0C\u6682\u65E0\u6700\u8FD1\u79EF\u5206\u6D41\u6C34\u8BB0\u5F55` : usage2 && usage2.items.length > 0 ? `\u5F53\u524D\u79EF\u5206 ${points.balance}\uFF0C\u6700\u8FD1\u6D41\u6C34\uFF1A${usage2.items.map((item) => `${item.title}${item.amount}\u5206${item.usedAt ? `\uFF08${item.usedAt}\uFF09` : ""}`).join("\uFF1B")}` : `\u5F53\u524D\u79EF\u5206 ${points.balance}\uFF0C\u6682\u672A\u67E5\u8BE2\u5230\u6700\u8FD1\u79EF\u5206\u6D41\u6C34`
1157
+ };
1158
+ }
1159
+ const usage = await benefitsPort.listPointUsage(userId, 1, 10);
1160
+ const usageText = usage.items.length > 0 ? `\uFF0C\u6700\u8FD1\u6D41\u6C34\uFF1A${usage.items.map((item) => `${item.title}${item.amount}\u5206${item.usedAt ? `\uFF08${item.usedAt}\uFF09` : ""}`).join("\uFF1B")}` : "\uFF0C\u6682\u672A\u67E5\u8BE2\u5230\u6700\u8FD1\u79EF\u5206\u6D41\u6C34";
1161
+ if (!card) {
1162
+ return {
1163
+ cards: [],
1164
+ text: `\u5F53\u524D\u79EF\u5206 ${points.balance}${points.membershipLabel ? `\uFF0C\u4F1A\u5458\u5361 ${points.membershipLabel}` : ""}${usageText}`
1165
+ };
1166
+ }
1167
+ return {
1168
+ cards: [{ cardType: "member-card-summary", data: {
1169
+ userId,
1170
+ cardRef: card.cardRef,
1171
+ cardDisplay: card.cardDisplay,
1172
+ status: card.status,
1173
+ ...card.levelName ? { levelName: card.levelName } : {},
1174
+ availablePoints: points.balance,
1175
+ ...card.cardImageUrl ? { cardImageUrl: card.cardImageUrl } : {},
1176
+ ...card.pointsRuleImageUrl ? { pointsRuleImageUrl: card.pointsRuleImageUrl } : {},
1177
+ ...card.pointsRuleContent ? { pointsRuleContent: card.pointsRuleContent } : {},
1178
+ ...card.pointsUrl ? { pointsUrl: card.pointsUrl } : {},
1179
+ ...card.presentation ? { presentation: card.presentation } : {},
1180
+ ...card.progress ? { progress: card.progress } : {},
1181
+ ...card.benefits ? { benefits: [...card.benefits] } : {}
1182
+ } }],
1183
+ text: `\u5F53\u524D\u79EF\u5206 ${points.balance}${points.membershipLabel ? `\uFF0C\u4F1A\u5458\u5361 ${points.membershipLabel}` : ""}${usageText}`
1184
+ };
1185
+ }
1186
+ default: {
1187
+ return { cards: [], text: "\u4E0D\u652F\u6301\u7684\u4F1A\u5458\u64CD\u4F5C" };
1188
+ }
1189
+ }
1190
+ }
1191
+ return { cards: [], text: MEMBER_QUERY_NOTICES[status.outcome] };
1192
+ }
1193
+ };
1194
+ }
1195
+
1196
+ // src/server/action.ts
1197
+ import { createHash } from "crypto";
1198
+ import { createAlipayUserAuthorization, InMemoryIdempotencyStore } from "@tbox.cn/app-sdk/server";
1199
+ import { parseMallScope as parseMallScope2 } from "@tbox.cn/app-contracts-mall";
1200
+ import { z as z2 } from "zod";
1201
+ var MEMBER_PROFILE_NAMESPACE = "member.enrollment.profile";
1202
+ var PROFILE_TTL_MS = 30 * 60 * 1e3;
1203
+ var MOBILE_PATTERN = /^1[3-9]\d{9}$/;
1204
+ var ENROLLMENT_FORM_VERSION = "v2026.08";
1205
+ var ENROLLMENT_CONSENTS = [
1206
+ { consentId: "privacy", version: "1", title: "\u9690\u79C1\u653F\u7B56", required: true },
1207
+ { consentId: "membership-terms", version: "1", title: "\u4F1A\u5458\u670D\u52A1\u6761\u6B3E", required: false }
1208
+ ];
1209
+ var enrollmentIdempotency = new InMemoryIdempotencyStore();
1210
+ function buildEnrollmentIdempotencyKey(userId, mallId, formVersion) {
1211
+ return createHash("sha256").update(`${userId}\0${mallId}\0${formVersion}`).digest("hex");
1212
+ }
1213
+ function maskMobile(mobile) {
1214
+ return mobile.length >= 7 ? `${mobile.slice(0, 3)}****${mobile.slice(-4)}` : "***";
1215
+ }
1216
+ function mallIdOf(ctx) {
1217
+ return parseMallScope2(ctx.attributes)?.mallId ?? "unknown";
1218
+ }
1219
+ function buildFormCard(input) {
1220
+ return {
1221
+ cardType: "member-enrollment-form",
1222
+ data: {
1223
+ userId: input.userId,
1224
+ state: input.state,
1225
+ formVersion: ENROLLMENT_FORM_VERSION,
1226
+ fields: [
1227
+ {
1228
+ fieldId: "mobile",
1229
+ label: "\u624B\u673A\u53F7",
1230
+ type: "tel",
1231
+ required: true,
1232
+ ...input.authorization.status === "authorized" ? { readOnly: true, prefill: input.prefilled?.mobile ?? "", placeholder: maskMobile(input.authorization.mobileMasked ?? "") } : { placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7" }
1233
+ },
1234
+ { fieldId: "displayName", label: "\u59D3\u540D", type: "text", required: false, ...input.prefilled?.displayName ? { prefill: input.prefilled.displayName } : {} },
1235
+ ...input.prefilled?.birthday ? [{ fieldId: "birthday", label: "\u751F\u65E5", type: "date", required: false, prefill: input.prefilled.birthday }] : []
1236
+ ],
1237
+ consents: ENROLLMENT_CONSENTS,
1238
+ authorization: input.authorization,
1239
+ ...input.manualNotice ? { manualNotice: input.manualNotice } : {}
1240
+ }
1241
+ };
1242
+ }
1243
+ function createMemberActions(ctx, resolveMember) {
1244
+ const state = ctx.state;
1245
+ return {
1246
+ /** 1. 发起支付宝授权(效果动作;resultActionId 由 dispatcher 补签校验) */
1247
+ requestAuthorization: {
1248
+ actionId: "member.request-alipay-authorization",
1249
+ moduleId: "member",
1250
+ cardType: "member-enrollment-form",
1251
+ label: "\u652F\u4ED8\u5B9D\u6388\u6743\u5E76\u586B\u5145",
1252
+ singleUse: true,
1253
+ inputSchema: z2.object({}).passthrough(),
1254
+ async execute() {
1255
+ return {
1256
+ text: "\u6B63\u5728\u62C9\u8D77\u652F\u4ED8\u5B9D\u6388\u6743\u2026",
1257
+ effects: [
1258
+ {
1259
+ type: "authorization.alipay-user-profile",
1260
+ scopes: ["auth_user"],
1261
+ resultActionId: "member.complete-alipay-authorization"
1262
+ }
1263
+ ]
1264
+ };
1265
+ }
1266
+ },
1267
+ /** 2. 授权回调(client getAuthCode → status/authCode 回传;补签 grant 单次消费;不渲染按钮) */
1268
+ completeAuthorization: {
1269
+ actionId: "member.complete-alipay-authorization",
1270
+ moduleId: "member",
1271
+ cardType: "member-enrollment-form",
1272
+ label: "\u6388\u6743\u7ED3\u679C\u56DE\u8C03",
1273
+ singleUse: true,
1274
+ hidden: true,
1275
+ inputSchema: z2.object({
1276
+ status: z2.enum(["success", "cancel", "failure", "unavailable"]),
1277
+ authCode: z2.string().optional()
1278
+ }),
1279
+ async execute(input, reqCtx) {
1280
+ const { status, authCode } = input ?? {};
1281
+ const userId = reqCtx?.identity?.userId ?? "";
1282
+ if (!userId) {
1283
+ return { text: "\u6388\u6743\u5931\u8D25\uFF1A\u7F3A\u5C11\u4F1A\u8BDD\u8EAB\u4EFD" };
1284
+ }
1285
+ if (status !== "success" || !authCode) {
1286
+ return {
1287
+ card: buildFormCard({
1288
+ userId,
1289
+ state: "not_enrolled",
1290
+ authorization: { status: "unavailable" },
1291
+ manualNotice: status === "unavailable" ? "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u652F\u4ED8\u5B9D\u6388\u6743\uFF0C\u53EF\u624B\u52A8\u586B\u5199\u624B\u673A\u53F7\u5165\u4F1A" : "\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u53EF\u624B\u52A8\u586B\u5199\u624B\u673A\u53F7\u5165\u4F1A"
1292
+ }),
1293
+ text: "\u6388\u6743\u672A\u5B8C\u6210\uFF0C\u5DF2\u5207\u6362\u624B\u52A8\u586B\u5199\u6A21\u5F0F"
1294
+ };
1295
+ }
1296
+ let profile;
1297
+ try {
1298
+ const authorized = await createAlipayUserAuthorization().authorizeUserProfile(authCode);
1299
+ profile = {
1300
+ ...authorized.mobile ? { mobile: authorized.mobile } : {},
1301
+ ...authorized.displayName ? { displayName: authorized.displayName } : {},
1302
+ ...authorized.gender ? { gender: authorized.gender } : {},
1303
+ ...authorized.birthday ? { birthday: authorized.birthday } : {}
1304
+ };
1305
+ } catch (e) {
1306
+ ctx.logger?.warn(`\u6388\u6743\u5B8C\u6210\u964D\u7EA7\u624B\u52A8\u5361\uFF1Aerr=${e.message}`);
1307
+ return {
1308
+ card: buildFormCard({
1309
+ userId,
1310
+ state: "not_enrolled",
1311
+ authorization: { status: "unavailable" },
1312
+ manualNotice: `\u6388\u6743\u8D44\u6599\u83B7\u53D6\u5931\u8D25\uFF08${e.message}\uFF09\uFF0C\u53EF\u624B\u52A8\u586B\u5199`
1313
+ }),
1314
+ text: "\u6388\u6743\u8D44\u6599\u83B7\u53D6\u5931\u8D25\uFF0C\u5DF2\u5207\u6362\u624B\u52A8\u586B\u5199\u6A21\u5F0F"
1315
+ };
1316
+ }
1317
+ await state.put(MEMBER_PROFILE_NAMESPACE, userId, profile, PROFILE_TTL_MS);
1318
+ return {
1319
+ card: buildFormCard({
1320
+ userId,
1321
+ state: "not_enrolled",
1322
+ authorization: { status: "authorized", mobileMasked: profile.mobile ?? "" },
1323
+ prefilled: profile
1324
+ }),
1325
+ text: "\u6388\u6743\u6210\u529F\uFF0C\u5DF2\u4E3A\u4F60\u9884\u586B\u624B\u673A\u53F7\uFF0C\u786E\u8BA4\u65E0\u8BEF\u540E\u63D0\u4EA4\u5165\u4F1A"
1326
+ };
1327
+ }
1328
+ },
1329
+ /** 3. 提交入会(身份解析 + consent 校验 + 幂等 + PII consume) */
1330
+ enroll: {
1331
+ actionId: "member.enroll",
1332
+ moduleId: "member",
1333
+ cardType: "member-enrollment-form",
1334
+ label: "\u63D0\u4EA4\u5165\u4F1A",
1335
+ singleUse: true,
1336
+ inputSchema: z2.object({
1337
+ fieldValues: z2.record(z2.string(), z2.string()).optional(),
1338
+ acceptedConsentIds: z2.array(z2.string()).optional(),
1339
+ formVersion: z2.string().optional()
1340
+ }),
1341
+ async execute(input, reqCtx) {
1342
+ const { fieldValues = {}, acceptedConsentIds = [] } = input ?? {};
1343
+ const userId = reqCtx?.identity?.userId ?? "";
1344
+ if (!userId) {
1345
+ return {
1346
+ card: { cardType: "member-enrollment-status", data: { userId: "", state: "failed", message: "\u7F3A\u5C11\u4F1A\u8BDD\u8EAB\u4EFD" } },
1347
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u7F3A\u5C11\u8EAB\u4EFD\u4FE1\u606F"
1348
+ };
1349
+ }
1350
+ const member = await resolveMember(reqCtx);
1351
+ if (!member) {
1352
+ return {
1353
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u4F1A\u5458\u670D\u52A1\u4E0D\u53EF\u7528" } },
1354
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u4F1A\u5458\u670D\u52A1\u4E0D\u53EF\u7528"
1355
+ };
1356
+ }
1357
+ const missing = ENROLLMENT_CONSENTS.filter((c) => c.required && !acceptedConsentIds.includes(c.consentId));
1358
+ if (missing.length > 0) {
1359
+ return {
1360
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: `\u8BF7\u5148\u540C\u610F\uFF1A${missing.map((c) => c.title).join("\u3001")}` } },
1361
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u672A\u5B8C\u6210\u5FC5\u9009\u540C\u610F\u9879"
1362
+ };
1363
+ }
1364
+ const authorized = await state.consume(MEMBER_PROFILE_NAMESPACE, userId) ?? void 0;
1365
+ const manualMobile = fieldValues.mobile ?? "";
1366
+ const mobile = authorized?.mobile ?? manualMobile;
1367
+ const identity = authorized?.mobile ? "alipay_authorized" : "enrollment_submission";
1368
+ if (!mobile || !MOBILE_PATTERN.test(mobile)) {
1369
+ return {
1370
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u624B\u673A\u53F7\u683C\u5F0F\u4E0D\u6B63\u786E\uFF08\u6388\u6743\u9884\u586B\u5931\u6548\u8BF7\u91CD\u65B0\u6388\u6743\u6216\u624B\u586B\uFF09" } },
1371
+ text: "\u5165\u4F1A\u5931\u8D25\uFF1A\u624B\u673A\u53F7\u683C\u5F0F\u4E0D\u6B63\u786E"
1372
+ };
1373
+ }
1374
+ const idempotencyKey = buildEnrollmentIdempotencyKey(userId, mallIdOf(reqCtx), ENROLLMENT_FORM_VERSION);
1375
+ const payloadTag = createHash("sha256").update(`${identity}:${mobile}`).digest("hex").slice(0, 16);
1376
+ const acquired = await enrollmentIdempotency.tryAcquire(idempotencyKey, payloadTag);
1377
+ if (!acquired.acquired) {
1378
+ return {
1379
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u91CD\u590D\u63D0\u4EA4\uFF0C\u8BF7\u7A0D\u540E\u5728\u5165\u4F1A\u72B6\u6001\u4E2D\u5237\u65B0\u786E\u8BA4\u7ED3\u679C" } },
1380
+ text: "\u8BF7\u52FF\u91CD\u590D\u63D0\u4EA4\uFF0C\u53EF\u5237\u65B0\u67E5\u770B\u5165\u4F1A\u72B6\u6001"
1381
+ };
1382
+ }
1383
+ let result;
1384
+ try {
1385
+ result = await member.enroll({
1386
+ userId,
1387
+ identity,
1388
+ fieldValues: {
1389
+ ...fieldValues,
1390
+ mobile,
1391
+ ...fieldValues.displayName || authorized?.displayName ? { displayName: fieldValues.displayName || authorized?.displayName } : {},
1392
+ ...fieldValues.birthday || authorized?.birthday ? { birthday: fieldValues.birthday || authorized?.birthday } : {},
1393
+ ...fieldValues.gender || authorized?.gender ? { gender: fieldValues.gender || authorized?.gender } : {}
1394
+ },
1395
+ acceptedConsentIds,
1396
+ idempotencyKey,
1397
+ formVersion: ENROLLMENT_FORM_VERSION
1398
+ });
1399
+ } catch (e) {
1400
+ enrollmentIdempotency.release?.(idempotencyKey);
1401
+ ctx.logger?.warn(`\u5165\u4F1A\u5931\u8D25\uFF08\u5E42\u7B49\u5DF2\u91CA\u653E key=${idempotencyKey.slice(0, 8)}\u2026\uFF09\uFF1Aerr=${e.message}`);
1402
+ return {
1403
+ card: { cardType: "member-enrollment-status", data: { userId, state: "failed", message: "\u5165\u4F1A\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" } },
1404
+ text: "\u5165\u4F1A\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"
1405
+ };
1406
+ }
1407
+ const cardForResult = (r) => ({
1408
+ cardType: "member-enrollment-status",
1409
+ data: r.status === "active" ? { userId, state: "active", memberRef: r.memberRef, message: "\u5165\u4F1A\u6210\u529F" } : r.status === "pending_confirmation" ? { userId, attemptRef: r.attemptRef, state: "pending_confirmation", message: "\u5DF2\u63D0\u4EA4\uFF0C\u7B49\u5F85\u5382\u5546\u786E\u8BA4\uFF08\u53EF\u70B9\u51FB\u5237\u65B0\u72B6\u6001\u67E5\u770B\u7ED3\u679C\uFF09" } : { userId, state: "failed", message: r.reason ?? "\u5165\u4F1A\u88AB\u62D2\u7EDD" }
1410
+ });
1411
+ await enrollmentIdempotency.commit(idempotencyKey, result);
1412
+ return {
1413
+ card: cardForResult(result),
1414
+ text: result.status === "active" ? "\u5165\u4F1A\u6210\u529F\uFF0C\u6B22\u8FCE\u52A0\u5165\uFF01" : result.status === "pending_confirmation" ? "\u5DF2\u63D0\u4EA4\u5165\u4F1A\u7533\u8BF7\uFF0C\u5382\u5546\u786E\u8BA4\u4E2D" : `\u5165\u4F1A\u5931\u8D25\uFF1A${result.status === "rejected" ? result.reason : "\u672A\u77E5\u539F\u56E0"}`
1415
+ };
1416
+ }
1417
+ },
1418
+ /** 4. 入会状态显式刷新(R4:无自动轮询) */
1419
+ queryEnrollmentStatus: {
1420
+ actionId: "member.query-enrollment-status",
1421
+ moduleId: "member",
1422
+ cardType: "member-enrollment-status",
1423
+ label: "\u5237\u65B0\u72B6\u6001",
1424
+ singleUse: true,
1425
+ inputSchema: z2.object({ attemptRef: z2.string().min(1) }),
1426
+ async execute(input, reqCtx) {
1427
+ const { attemptRef } = input ?? {};
1428
+ const userId = reqCtx?.identity?.userId ?? "";
1429
+ const member = await resolveMember(reqCtx);
1430
+ if (!userId || !attemptRef || !member) {
1431
+ return {
1432
+ update: {
1433
+ data: {
1434
+ userId,
1435
+ state: "failed",
1436
+ message: "\u7F3A\u5C11\u5165\u4F1A\u8BB0\u5F55\u5F15\u7528"
1437
+ }
1438
+ }
1439
+ };
1440
+ }
1441
+ const status = await member.getEnrollmentStatus(userId, attemptRef);
1442
+ return {
1443
+ update: {
1444
+ data: {
1445
+ userId,
1446
+ attemptRef,
1447
+ state: status.status,
1448
+ ...status.memberRef ? { memberRef: status.memberRef } : {},
1449
+ ...status.status === "active" ? { message: "\u5165\u4F1A\u6210\u529F\uFF0C\u6B22\u8FCE\u52A0\u5165\uFF01" } : status.status === "pending_confirmation" ? { message: "\u5382\u5546\u786E\u8BA4\u4E2D\uFF0C\u53EF\u7A0D\u540E\u518D\u5237\u65B0" } : { message: status.reason ?? "\u5165\u4F1A\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u63D0\u4EA4" }
1450
+ }
1451
+ }
1452
+ };
1453
+ }
1454
+ }
1455
+ };
1456
+ }
1457
+
1458
+ // src/server/subject-binding.ts
1459
+ import { createHash as createHash2 } from "crypto";
1460
+ function createSubjectBinding(ctx) {
1461
+ return {
1462
+ resolveExternalMemberRef(userId) {
1463
+ void ctx;
1464
+ return userId ? `member-ref:${createHash2("sha256").update(userId).digest("hex").slice(0, 24)}` : void 0;
1465
+ }
1466
+ };
1467
+ }
1468
+
1469
+ // src/server/cards/index.ts
1470
+ var memberCards = {
1471
+ "member-info": meta_default,
1472
+ "member-card-summary": meta_default2,
1473
+ "member-point-usage-list": meta_default3,
1474
+ "member-enrollment-form": meta_default4,
1475
+ "member-enrollment-status": meta_default5,
1476
+ "member-enrollment-cta": meta_default6,
1477
+ "member-login-cta": meta_default7
1478
+ };
1479
+
1480
+ // src/server/index.ts
1481
+ var serverCtx;
1482
+ var getCtx = () => {
1483
+ if (!serverCtx) throw new Error("member \u6A21\u5757\u672A\u6CE8\u518C\uFF08ServerContext \u672A\u7ED1\u5B9A\uFF09");
1484
+ return serverCtx;
1485
+ };
1486
+ var memberPorts = {
1487
+ getAccount: (reqCtx) => resolveMemberAccountPort(reqCtx, getCtx()),
1488
+ getProfile: (reqCtx) => resolveMemberProfilePort(reqCtx, getCtx()),
1489
+ getCard: (reqCtx) => resolveMemberCardPort(reqCtx, getCtx()),
1490
+ getBenefits: (reqCtx) => resolveMemberBenefitsPort(reqCtx, getCtx()),
1491
+ getEnrollment: (reqCtx) => resolveMemberEnrollmentPort(reqCtx, getCtx())
1492
+ };
1493
+ var queryPointsTool = withEarlyExecution(
1494
+ withCardEmissionHint(createQueryPointsTool(memberPorts))
1495
+ );
1496
+ var queryMemberInfoTool = withEarlyExecution(
1497
+ withCardEmissionHint(createQueryMemberInfoTool(memberPorts))
1498
+ );
1499
+ var queryMemberCardTool = withEarlyExecution(
1500
+ withCardEmissionHint(createQueryMemberCardTool(memberPorts))
1501
+ );
1502
+ var queryEnrollmentFormTool = withCardEmissionHint(createQueryEnrollmentFormTool(memberPorts), {
1503
+ actions: true
1504
+ });
1505
+ var queryEnrollmentCtaTool = withCardEmissionHint(createQueryEnrollmentCtaTool(memberPorts), {
1506
+ actions: true
1507
+ });
1508
+ var queryLoginCtaTool = withCardEmissionHint(createQueryLoginCtaTool(memberPorts), {
1509
+ actions: true
1510
+ });
1511
+ var cards = {
1512
+ "member-info": meta_default,
1513
+ "member-card-summary": meta_default2,
1514
+ "member-point-usage-list": meta_default3,
1515
+ "member-enrollment-form": meta_default4,
1516
+ "member-enrollment-status": meta_default5,
1517
+ "member-enrollment-cta": meta_default6,
1518
+ "member-login-cta": meta_default7
1519
+ };
1520
+ var preTools = {};
1521
+ var skills = Object.fromEntries(
1522
+ (await loadModuleSkills("@tbox.cn/app-module-member", import.meta.url)).map((s) => [s.name, s])
1523
+ );
1524
+ var serverModule = {
1525
+ declaration: {
1526
+ moduleId: "member"
1527
+ },
1528
+ cards,
1529
+ preTools,
1530
+ skills,
1531
+ register(ctx) {
1532
+ serverCtx = ctx;
1533
+ ctx.services.register(
1534
+ "member.service",
1535
+ (reqCtx) => resolveMemberBenefitsPort(reqCtx, ctx)
1536
+ );
1537
+ ctx.services.register(
1538
+ "member.status",
1539
+ (reqCtx) => resolveMemberStatus(reqCtx, ctx)
1540
+ );
1541
+ ctx.tools.register(queryPointsTool);
1542
+ ctx.tools.register(queryMemberInfoTool);
1543
+ ctx.tools.register(queryMemberCardTool);
1544
+ ctx.tools.register(queryEnrollmentFormTool);
1545
+ ctx.tools.register(queryEnrollmentCtaTool);
1546
+ ctx.tools.register(queryLoginCtaTool);
1547
+ ctx.cards.registerMap(cards);
1548
+ ctx.handlers.register(createMemberHandler(ctx));
1549
+ const actions = createMemberActions(ctx, (reqCtx) => resolveMemberEnrollmentPort(reqCtx, ctx));
1550
+ for (const action of Object.values(actions)) {
1551
+ ctx.cardActions.register(action);
1552
+ }
1553
+ ctx.services.register("member.subject-binding", createSubjectBinding(ctx));
1554
+ ctx.services.register(
1555
+ MEMBER_SNAPSHOT_SERVICE,
1556
+ async (reqCtx) => {
1557
+ const [account, benefits, enrollment] = await Promise.all([
1558
+ resolveMemberAccountPort(reqCtx, ctx),
1559
+ resolveMemberBenefitsPort(reqCtx, ctx),
1560
+ resolveMemberEnrollmentPort(reqCtx, ctx).catch(() => null)
1561
+ ]);
1562
+ return {
1563
+ getAccount: (userId) => account.getAccount(userId),
1564
+ queryPoints: (userId) => benefits.queryPoints(userId),
1565
+ ...enrollment?.enrollUrl ? { enrollUrl: enrollment.enrollUrl } : {}
1566
+ };
1567
+ }
1568
+ );
1569
+ }
1570
+ };
1571
+
1572
+ export {
1573
+ InMemoryMemberService,
1574
+ resolveMemberAccountPort,
1575
+ resolveMemberProfilePort,
1576
+ resolveMemberCardPort,
1577
+ resolveMemberBenefitsPort,
1578
+ resolveMemberEnrollmentPort,
1579
+ memberCards,
1580
+ serverModule
1581
+ };