@@ -136,6 +136,7 @@ class ShopInBitService {
136136 final feeTicketNumber = category == ShopInBitCategory .car
137137 ? _extractFeeTicketNumber (apiMessages)
138138 : null ;
139+ final requestDescription = _extractRequestDescription (apiMessages);
139140
140141 final messages = apiMessages
141142 .map (
@@ -153,7 +154,7 @@ class ShopInBitService {
153154 category: Value (category),
154155 status: Value (mappedStatus),
155156 statusRaw: Value (statusResp.value! .stateRaw),
156- requestDescription: const Value ("" ),
157+ requestDescription: Value (requestDescription ),
157158 deliveryCountry: const Value ("" ),
158159 offerProductName: Value (offerProductName),
159160 offerPrice: Value (offerPrice),
@@ -180,22 +181,43 @@ class ShopInBitService {
180181 }
181182}
182183
183- // The API does not return service_type for existing tickets, so we infer
184- // category from the first user message. Stack Wallet's car flow always seeds
185- // the comment with this exact phrase; travel cannot be distinguished from
186- // concierge because Stack Wallet sends travel as service_type="concierge" too .
184+ // Infer category from the first user message. The car flow always seeds
185+ // the comment with the "car research fee" line; travel requests built by
186+ // _buildRequestDescription always start with "Arrangement: " followed by
187+ // structured labels. Both are fragile against template changes in the form .
187188final RegExp _kCarResearchFeeRegex = RegExp (r'car research fee \(#([^)]+)\)' );
189+ final RegExp _kTravelArrangementRegex = RegExp (
190+ r'^Arrangement:\s' ,
191+ multiLine: true ,
192+ );
188193
189194ShopInBitCategory _inferCategoryFromMessages (List <TicketMessage > messages) {
190195 final firstUser = messages.where ((m) => ! m.fromAgent).firstOrNull;
191196 if (firstUser == null ) return ShopInBitCategory .concierge;
192- return _kCarResearchFeeRegex.hasMatch (firstUser.content)
193- ? ShopInBitCategory .car
194- : ShopInBitCategory .concierge;
197+ final content = firstUser.content;
198+ if (_kCarResearchFeeRegex.hasMatch (content)) {
199+ return ShopInBitCategory .car;
200+ }
201+ if (_kTravelArrangementRegex.hasMatch (content)) {
202+ return ShopInBitCategory .travel;
203+ }
204+ return ShopInBitCategory .concierge;
195205}
196206
197207String ? _extractFeeTicketNumber (List <TicketMessage > messages) {
198208 final firstUser = messages.where ((m) => ! m.fromAgent).firstOrNull;
199209 if (firstUser == null ) return null ;
200210 return _kCarResearchFeeRegex.firstMatch (firstUser.content)? .group (1 );
201211}
212+
213+ // The original `comment` passed to POST /requests becomes the first user message.
214+ final RegExp _kHtmlTagRegex = RegExp (r'<[^>]+>' );
215+
216+ String _extractRequestDescription (List <TicketMessage > messages) {
217+ final firstUser = messages.where ((m) => ! m.fromAgent).firstOrNull;
218+ if (firstUser == null ) return "" ;
219+ return firstUser.content
220+ .replaceAll (RegExp (r'<br\s*/?>' , caseSensitive: false ), '\n ' )
221+ .replaceAll (_kHtmlTagRegex, '' )
222+ .trim ();
223+ }
0 commit comments