@payware/mcp-server 1.1.1 → 1.3.0

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 (127) hide show
  1. package/.env.example +0 -0
  2. package/README.md +22 -16
  3. package/check-keys.js +0 -0
  4. package/keys/README.md +0 -0
  5. package/mcp-config.json +0 -0
  6. package/package.json +69 -66
  7. package/payware-mcp-config.json +0 -0
  8. package/src/auth/rsa-keys.js +0 -0
  9. package/src/auth/sandbox-setup.js +186 -186
  10. package/src/config/env.js +0 -0
  11. package/src/config/partner-types.js +0 -0
  12. package/src/core/auth/jwt-factory.js +4 -4
  13. package/src/core/auth/jwt-token.js +107 -66
  14. package/src/core/utils/json-serializer.js +5 -5
  15. package/src/core/utils/key-utils.js +0 -0
  16. package/src/index.js +0 -0
  17. package/src/isv/data/index.js +0 -0
  18. package/src/isv/deep-links/delete-product-link.js +0 -0
  19. package/src/isv/deep-links/delete-transaction-link.js +0 -0
  20. package/src/isv/deep-links/get-product-link.js +0 -0
  21. package/src/isv/deep-links/get-transaction-link.js +0 -0
  22. package/src/isv/deep-links/index.js +0 -0
  23. package/src/isv/index.js +29 -1
  24. package/src/isv/invitations/index.js +471 -0
  25. package/src/isv/oauth2/create-token-simple.js +0 -0
  26. package/src/isv/oauth2/get-token-info.js +0 -0
  27. package/src/isv/oauth2/obtain-token.js +19 -8
  28. package/src/isv/oauth2/token-helper.js +5 -2
  29. package/src/isv/poi/cancel-price.js +2 -10
  30. package/src/isv/poi/create.js +19 -0
  31. package/src/isv/poi/get-poi.js +2 -10
  32. package/src/isv/poi/get-qrcode.js +2 -10
  33. package/src/isv/poi/get-status.js +2 -10
  34. package/src/isv/poi/index.js +5 -0
  35. package/src/isv/poi/list-pois.js +2 -10
  36. package/src/isv/poi/set-price.js +217 -225
  37. package/src/isv/products/audios.js +0 -0
  38. package/src/isv/products/create-product.js +313 -313
  39. package/src/isv/products/delete-product.js +0 -0
  40. package/src/isv/products/get-product-image.js +0 -0
  41. package/src/isv/products/get-product.js +0 -0
  42. package/src/isv/products/index.js +0 -0
  43. package/src/isv/products/list-products.js +0 -0
  44. package/src/isv/products/schedules.js +0 -0
  45. package/src/isv/products/update-product.js +264 -264
  46. package/src/isv/reference/index.js +249 -0
  47. package/src/isv/shops/index.js +22 -0
  48. package/src/isv/transactions/cancel-transaction.js +43 -12
  49. package/src/isv/transactions/create-transaction.js +22 -6
  50. package/src/isv/transactions/get-status.js +18 -11
  51. package/src/isv/transactions/process-transaction.js +160 -42
  52. package/src/isv/transactions/simulate-callback.js +128 -109
  53. package/src/isv/transactions/transaction-history.js +86 -90
  54. package/src/isv/utils/auth.js +2 -2
  55. package/src/mcp-proxy.js +0 -0
  56. package/src/merchant/data/export-report.js +0 -0
  57. package/src/merchant/data/generate-report.js +19 -1
  58. package/src/merchant/data/get-export-list.js +0 -0
  59. package/src/merchant/data/get-report-requests.js +0 -0
  60. package/src/merchant/data/get-report-status.js +0 -0
  61. package/src/merchant/data/index.js +0 -0
  62. package/src/merchant/data/list-reports.js +0 -0
  63. package/src/merchant/deep-links/delete-product-link.js +0 -0
  64. package/src/merchant/deep-links/delete-transaction-link.js +0 -0
  65. package/src/merchant/deep-links/get-product-link.js +0 -0
  66. package/src/merchant/deep-links/get-transaction-link.js +0 -0
  67. package/src/merchant/deep-links/index.js +0 -0
  68. package/src/merchant/index.js +21 -1
  69. package/src/merchant/logs/index.js +366 -0
  70. package/src/merchant/products/audios.js +0 -0
  71. package/src/merchant/products/create-product.js +287 -287
  72. package/src/merchant/products/delete-product.js +0 -0
  73. package/src/merchant/products/get-product-image.js +0 -0
  74. package/src/merchant/products/get-product.js +0 -0
  75. package/src/merchant/products/index.js +0 -0
  76. package/src/merchant/products/list-products.js +0 -0
  77. package/src/merchant/products/schedules.js +0 -0
  78. package/src/merchant/products/update-product.js +281 -281
  79. package/src/merchant/shops/index.js +18 -0
  80. package/src/merchant/transactions/cancel-transaction.js +16 -19
  81. package/src/merchant/transactions/create-transaction.js +99 -19
  82. package/src/merchant/transactions/get-status.js +41 -12
  83. package/src/merchant/transactions/process-transaction.js +8 -16
  84. package/src/merchant/transactions/simulate-callback.js +68 -9
  85. package/src/merchant/transactions/transaction-history.js +275 -257
  86. package/src/payment-institution/data/index.js +0 -0
  87. package/src/payment-institution/deep-links/delete-transaction-link.js +0 -0
  88. package/src/payment-institution/deep-links/get-transaction-link.js +0 -0
  89. package/src/payment-institution/deep-links/index.js +0 -0
  90. package/src/payment-institution/index.js +0 -0
  91. package/src/payment-institution/transactions/create-pi-transaction.js +6 -14
  92. package/src/payment-institution/transactions/finalize-transaction.js +333 -340
  93. package/src/payment-institution/transactions/get-transaction-history.js +3 -11
  94. package/src/payment-institution/transactions/get-transaction-status.js +39 -12
  95. package/src/payment-institution/transactions/process-pi-transaction.js +5 -13
  96. package/src/payment-institution/transactions/simulate-pi-callback.js +49 -9
  97. package/src/payment-institution/transactions/soundbite-transaction.js +5 -13
  98. package/src/proxy-server.js +0 -0
  99. package/src/shared/api-errors.js +188 -0
  100. package/src/shared/auth-headers.js +62 -0
  101. package/src/shared/data/data-api.js +11 -10
  102. package/src/shared/deep-links/api.js +5 -4
  103. package/src/shared/index.js +0 -0
  104. package/src/shared/logs/logs-api.js +242 -0
  105. package/src/shared/poi/poi-create-api.js +419 -0
  106. package/src/shared/products/products-api.js +17 -16
  107. package/src/shared/shops/shops-api.js +155 -0
  108. package/src/tools/advanced-code-generator.js +2255 -2246
  109. package/src/utils/code-examples/common/auth-examples.js +1484 -1481
  110. package/src/utils/code-examples/common/helpers.js +7 -4
  111. package/src/utils/code-examples/common/utils-examples.js +0 -0
  112. package/src/utils/code-examples/frameworks/examples.js +4 -4
  113. package/src/utils/code-examples/index.js +0 -0
  114. package/src/utils/code-examples/isv/oauth2.js +458 -600
  115. package/src/utils/code-examples/isv/products.js +8 -8
  116. package/src/utils/code-examples/isv/transactions.js +815 -812
  117. package/src/utils/code-examples/merchant/products.js +0 -0
  118. package/src/utils/code-examples/merchant/transactions.js +9 -9
  119. package/src/utils/code-examples/payment-institution/transactions.js +0 -0
  120. package/src/utils/code-examples/shared/data.js +0 -0
  121. package/src/utils/code-examples/shared/deep-links.js +0 -0
  122. package/src/utils/code-examples/shared/soundbites.js +0 -0
  123. package/src/utils/code-examples.js +24 -24
  124. package/src/utils/format-request.js +3 -3
  125. package/src/utils/json-formatter.js +20 -20
  126. package/src/utils/server-info.js +0 -0
  127. package/MCP-SETUP.md +0 -190
@@ -1,22 +1,35 @@
1
1
  import axios from 'axios';
2
2
  import { createJWTForPartner } from '../../core/auth/jwt-factory.js';
3
3
  import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
4
+ import { describeApiError } from '../../shared/api-errors.js';
4
5
 
5
6
  /**
6
- * Get transaction history from payware API as an ISV
7
+ * Get one completed transaction as an ISV acting on behalf of a merchant.
8
+ *
9
+ * **This used to be a list-and-filter tool, and there is nothing on the server to list.**
10
+ * It built `GET /transactions-history?limit=50&offset=0&status=...&from=...&to=...` and read
11
+ * `result.transactions` / `result.totalCount` off the response. `TransactionHistoryController` maps
12
+ * exactly one route - `GET /api/transactions-history/{transactionId}` - so the bare path matched no
13
+ * handler and the call answered 404 every time, whatever the filters said. The filters, the
14
+ * pagination and the result shape were all describing an endpoint that has never existed.
15
+ *
16
+ * It now does what the merchant tool does and what the API offers: fetch one transaction by id.
7
17
  */
8
18
  export async function getTransactionHistory({
9
19
  merchantPartnerId,
10
20
  oauth2Token,
11
- partnerId,
12
21
  transactionId,
13
- limit = 50,
14
- offset = 0,
15
- status,
16
- from,
17
- to,
18
22
  useSandbox = true
19
23
  }) {
24
+ if (!transactionId) {
25
+ throw new Error(
26
+ 'transactionId is required. The payware API has no endpoint that lists or filters transaction ' +
27
+ 'history - GET /transactions-history/{transactionId} fetches one transaction by id, and that ' +
28
+ 'is the whole surface. Keep your own record of the ids you created, or read them from the ' +
29
+ 'callbacks you received.'
30
+ );
31
+ }
32
+
20
33
  if (!merchantPartnerId) {
21
34
  throw new Error('Merchant Partner ID is required for ISV operations');
22
35
  }
@@ -28,6 +41,7 @@ export async function getTransactionHistory({
28
41
  const isvPartnerId = getPartnerIdSafe();
29
42
  const privateKey = getPrivateKeySafe(useSandbox);
30
43
 
44
+ // GET, so no body and no content hash.
31
45
  const jwtData = await createJWTForPartner({
32
46
  partnerId: isvPartnerId,
33
47
  privateKey,
@@ -36,19 +50,9 @@ export async function getTransactionHistory({
36
50
  oauth2Token
37
51
  });
38
52
 
39
- const params = new URLSearchParams();
40
- if (partnerId) params.append('partnerId', partnerId);
41
- if (transactionId) params.append('transactionId', transactionId);
42
- if (limit) params.append('limit', limit.toString());
43
- if (offset) params.append('offset', offset.toString());
44
- if (status) params.append('status', status);
45
- if (from) params.append('from', from);
46
- if (to) params.append('to', to);
47
-
48
53
  try {
49
54
  const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
50
- const url = `${baseUrl}/transactions-history?${params.toString()}`;
51
- const response = await axios.get(url, {
55
+ const response = await axios.get(`${baseUrl}/transactions-history/${transactionId}`, {
52
56
  headers: {
53
57
  'Authorization': `Bearer ${jwtData.token}`,
54
58
  'Content-Type': 'application/json',
@@ -58,62 +62,50 @@ export async function getTransactionHistory({
58
62
 
59
63
  return response.data;
60
64
  } catch (error) {
61
- if (error.response) {
62
- throw new Error(`payware API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
63
- }
64
- throw error;
65
+ throw describeApiError(error, 'call the payware API');
65
66
  }
66
67
  }
67
68
 
68
69
  export const getTransactionHistoryTool = {
69
70
  name: 'payware_operations_get_transaction_history',
70
- description: `Get transaction history as an ISV on behalf of a merchant.
71
+ description: `Get one completed or expired transaction as an ISV on behalf of a merchant.
72
+
73
+ **Endpoint:** GET /transactions-history/{transactionId}
74
+
75
+ Returns a transaction in a final state: CONFIRMED, DECLINED, FAILED, EXPIRED or CANCELLED. For a
76
+ transaction still ACTIVE, use \`payware_operations_get_transaction_status\` instead - the two
77
+ endpoints cover disjoint sets of transactions and neither falls back to the other.
78
+
79
+ ⚠️ **There is no way to list or search transaction history.** This endpoint takes an id and returns
80
+ that one transaction. There is no pagination, no date range and no status filter anywhere in the
81
+ payware API - so keep your own record of the transaction ids you created, or read them from the
82
+ callbacks you received. (This tool previously advertised \`limit\`, \`offset\`, \`status\`, \`from\` and
83
+ \`to\` parameters; they described an endpoint that does not exist and every call 404'd.)
84
+
85
+ 📦 **This is the only source of the delivery address.** For a SHIPPABLE transaction,
86
+ \`deliveryAddress\` is returned here and nowhere else - it was removed from the finalized callback on
87
+ 2026-08-07, because a callback goes to whatever certificate the merchant's endpoint presents and a
88
+ shopper's home address does not belong on that channel. The callback tells you the shipment is
89
+ ready; this call gives you the address.
71
90
 
72
- Retrieves both active and completed transactions with optional filtering.
73
- Requires ISV authentication with merchant partner ID and OAuth2 token.`,
91
+ ⚠️ **The MERCHANT's plan governs, and this endpoint is now gated.** It was ungated until 2026-08-21
92
+ and now requires Standard or Premium, so acting for a **Basic** merchant answers **403**.`,
74
93
 
75
94
  inputSchema: {
76
95
  type: 'object',
77
- required: ['merchantPartnerId', 'oauth2Token'],
96
+ required: ['transactionId', 'merchantPartnerId', 'oauth2Token'],
78
97
  properties: {
79
- merchantPartnerId: {
80
- type: 'string',
81
- description: 'Partner ID of the target merchant'
82
- },
83
- oauth2Token: {
84
- type: 'string',
85
- description: 'OAuth2 access token obtained from the merchant'
86
- },
87
- partnerId: {
88
- type: 'string',
89
- description: 'Filter by partner ID (optional)'
90
- },
91
98
  transactionId: {
92
99
  type: 'string',
93
- description: 'Filter by specific transaction ID (optional)'
94
- },
95
- limit: {
96
- type: 'number',
97
- default: 50,
98
- description: 'Maximum number of results to return'
99
- },
100
- offset: {
101
- type: 'number',
102
- default: 0,
103
- description: 'Number of results to skip for pagination'
100
+ description: 'The transaction to fetch, e.g. "pw7e4rCToG". Required - there is no list or search endpoint.'
104
101
  },
105
- status: {
106
- type: 'string',
107
- enum: ['ACTIVE', 'CONFIRMED', 'DECLINED', 'FAILED', 'EXPIRED', 'CANCELLED'],
108
- description: 'Filter by transaction status'
109
- },
110
- from: {
102
+ merchantPartnerId: {
111
103
  type: 'string',
112
- description: 'Start date for filtering (YYYY-MM-DD format)'
104
+ description: 'Partner ID of the target merchant (8 alphanumeric characters)'
113
105
  },
114
- to: {
106
+ oauth2Token: {
115
107
  type: 'string',
116
- description: 'End date for filtering (YYYY-MM-DD format)'
108
+ description: 'OAuth2 access token obtained from the merchant'
117
109
  },
118
110
  useSandbox: {
119
111
  type: 'boolean',
@@ -125,46 +117,46 @@ Requires ISV authentication with merchant partner ID and OAuth2 token.`,
125
117
 
126
118
  async handler(params) {
127
119
  try {
128
- const result = await getTransactionHistory(params);
129
-
130
- const transactions = result.transactions || [];
131
- const totalCount = result.totalCount || transactions.length;
132
-
133
- const formatTransaction = (tx) => {
134
- const statusEmojis = {
135
- 'ACTIVE': '⏳',
136
- 'CONFIRMED': '✅',
137
- 'DECLINED': '❌',
138
- 'FAILED': '⚠️',
139
- 'EXPIRED': '⏰',
140
- 'CANCELLED': '🚫'
141
- };
142
- const emoji = statusEmojis[tx.status] || '❓';
143
- const paymentInfo = tx.paymentMethod ? ` [${tx.paymentMethod}]` : '';
144
- return ` ${emoji} **${tx.id}** - ${tx.trData?.amount || '0.00'} ${tx.trData?.currency || 'EUR'}${paymentInfo} - ${tx.status} - ${tx.createdAt || 'N/A'}`;
120
+ const tx = await getTransactionHistory(params);
121
+
122
+ const statusEmojis = {
123
+ CONFIRMED: '✅',
124
+ DECLINED: '❌',
125
+ FAILED: '⚠️',
126
+ EXPIRED: '⏰',
127
+ CANCELLED: '🚫'
145
128
  };
129
+ const emoji = statusEmojis[tx.status] || '❓';
130
+
131
+ const delivery = tx.deliveryAddress ? `
132
+
133
+ 📦 **Delivery Address** (SHIPPABLE)
134
+ - Name: ${tx.deliveryAddress.fullName || 'N/A'}
135
+ - Address: ${tx.deliveryAddress.streetAddressLine1 || 'N/A'}${tx.deliveryAddress.streetAddressLine2 ? `, ${tx.deliveryAddress.streetAddressLine2}` : ''}
136
+ - City: ${tx.deliveryAddress.city || 'N/A'}, ${tx.deliveryAddress.zipCode || 'N/A'}
137
+ - Region: ${tx.deliveryAddress.region || 'N/A'}
138
+ - Country: ${tx.deliveryAddress.country || 'N/A'}
139
+ - Phone: ${tx.deliveryAddress.phoneNumber || 'N/A'}
140
+ - Email: ${tx.deliveryAddress.email || 'N/A'}` : '';
146
141
 
147
142
  return {
148
143
  content: [{
149
144
  type: 'text',
150
- text: `📈 **Transaction History Retrieved (ISV -> Merchant: ${params.merchantPartnerId})**
145
+ text: `${emoji} **Transaction ${tx.transactionId || params.transactionId}** - ${tx.status}
151
146
 
152
- **ISV Integration:**
153
- - ISV Partner: ${getPartnerIdSafe()}
154
- - Target Merchant: ${params.merchantPartnerId}
155
- - OAuth2 Token: ${params.oauth2Token.substring(0, 8)}...
147
+ **ISV -> Merchant:** ${getPartnerIdSafe()} -> ${params.merchantPartnerId}
156
148
 
157
- **Results:** ${transactions.length} of ${totalCount} transactions
158
-
159
- **Transactions:**
160
- ${transactions.length > 0 ? transactions.map(formatTransaction).join('\n') : ' No transactions found'}
161
-
162
- **Applied Filters:**
163
- ${params.status ? `- Status: ${params.status}\n` : ''}${params.from ? `- From: ${params.from}\n` : ''}${params.to ? `- To: ${params.to}\n` : ''}${params.limit ? `- Limit: ${params.limit}\n` : ''}${params.offset ? `- Offset: ${params.offset}\n` : ''}
149
+ - Amount: ${tx.amount ?? 'N/A'} ${tx.currency ?? ''}
150
+ - Fee: ${tx.fee ?? '(not reported - absence means unknown, not zero)'}
151
+ - Type: ${tx.transactionType ?? 'N/A'}
152
+ - Payment method: ${tx.paymentMethod ?? 'N/A'}
153
+ - Status message: ${tx.statusMessage ?? 'N/A'}
154
+ - Created: ${tx.created ?? 'N/A'}
155
+ - Finalized: ${tx.finalized ?? '(absent - EXPIRED transactions carry no finalization moment)'}${delivery}
164
156
 
165
157
  **Raw Response:**
166
158
  \`\`\`json
167
- ${JSON.stringify(result, null, 2)}
159
+ ${JSON.stringify(tx, null, 2)}
168
160
  \`\`\``
169
161
  }]
170
162
  };
@@ -179,9 +171,13 @@ ${JSON.stringify(result, null, 2)}
179
171
  **ISV Authentication:**
180
172
  - Merchant Partner ID: ${params.merchantPartnerId}
181
173
  - OAuth2 Token: ${params.oauth2Token ? 'Provided' : 'Missing'}
182
- - ISV Partner ID: ${getPartnerIdSafe()}`
174
+ - ISV Partner ID: ${getPartnerIdSafe()}
175
+
176
+ **Common causes:**
177
+ - 404: the transaction is still ACTIVE (use \`payware_operations_get_transaction_status\`), or the id is wrong.
178
+ - 403: the acting merchant is on the Basic plan, which does not include transaction history.`
183
179
  }]
184
180
  };
185
181
  }
186
182
  }
187
- };
183
+ };
@@ -80,11 +80,11 @@ async function createOAuth2JWT({ isvPartnerId, privateKey, requestBody, audience
80
80
  typ: 'JWT'
81
81
  };
82
82
 
83
- // Add contentMd5 to header if request body exists
83
+ // Add contentSha256 to header if request body exists (SHA-256 is preferred over MD5)
84
84
  if (requestBody) {
85
85
  // Use standard JSON.stringify to match axios serialization (preserves property order)
86
86
  const bodyString = JSON.stringify(requestBody);
87
- header.contentMd5 = crypto.default.createHash('md5').update(bodyString, 'utf8').digest('base64');
87
+ header.contentSha256 = crypto.default.createHash('sha256').update(bodyString, 'utf8').digest('base64');
88
88
  }
89
89
 
90
90
  // JWT Payload for OAuth2 requests
package/src/mcp-proxy.js CHANGED
File without changes
File without changes
@@ -6,7 +6,25 @@ import { getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
6
6
  */
7
7
  export const generateReportTool = {
8
8
  name: "payware_data_generate_report",
9
- description: "Generate an asynchronous data report for merchant data analysis and reporting. Production only - not supported in sandbox.",
9
+ description: `Generate an asynchronous data report for merchant data analysis and reporting. Production only - not supported in sandbox.
10
+
11
+ ⚠️ **Running a report is entitlement-checked as of 2026-08-22.** Naming a report answers **403
12
+ \`ERR_REPORT_NOT_AVAILABLE\`** when that report is not included in the caller's plan or partner type.
13
+ Until that date only the *menu* was filtered - the door was not - so a caller that hard-coded a
14
+ report id it had never been offered could still run it, and may now start failing.
15
+
16
+ **Always resolve the id from \`payware_data_list_reports\` rather than hard-coding one.** That call
17
+ and this check share a single rule, so the menu and the door cannot drift apart: if a report is in
18
+ that list, this call will accept it.
19
+
20
+ Distinguish the two failures:
21
+ - **403 \`ERR_REPORT_NOT_AVAILABLE\`** - the report exists, this caller may not run it. Not retryable;
22
+ it is a plan boundary, not a transient error.
23
+ - **404 \`ERR_REPORT_UNIT_ID_NOT_FOUND\`** - no report is registered under that id at all. Usually a
24
+ typo or a stale id.
25
+
26
+ For an ISV acting on behalf of a merchant, the plan that applies is the **merchant's**, and reports
27
+ are outside the Basic plan entirely.`,
10
28
  inputSchema: {
11
29
  type: "object",
12
30
  properties: {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -20,6 +20,12 @@ import { merchantProductTools } from './products/index.js';
20
20
  // Deep links tools
21
21
  import { merchantDeepLinksTools } from './deep-links/index.js';
22
22
 
23
+ // Logs tools (Premium feature)
24
+ import { merchantLogsTools } from './logs/index.js';
25
+
26
+ // Shop tools - needed before any call that takes a shop / shopCode
27
+ import { merchantShopTools } from './shops/index.js';
28
+
23
29
  // Shared utility tools
24
30
  import { getServerInfoTool } from '../utils/server-info.js';
25
31
 
@@ -48,7 +54,13 @@ export const merchantTools = [
48
54
  ...merchantProductTools,
49
55
 
50
56
  // Deep links management
51
- ...merchantDeepLinksTools
57
+ ...merchantDeepLinksTools,
58
+
59
+ // Logs management (Premium feature)
60
+ ...merchantLogsTools,
61
+
62
+ // Shop lookup
63
+ ...merchantShopTools
52
64
  ];
53
65
 
54
66
  /**
@@ -83,6 +95,14 @@ export function getMerchantTools() {
83
95
  else if (tool.name.startsWith('payware_deep_links_')) {
84
96
  newName = tool.name; // Keep deep_links grouping
85
97
  }
98
+ // Logs tools keep their logs grouping
99
+ else if (tool.name.startsWith('payware_logs_')) {
100
+ newName = tool.name; // Keep logs grouping
101
+ }
102
+ // Shop tools keep their shops grouping
103
+ else if (tool.name.startsWith('payware_shops_')) {
104
+ newName = tool.name; // Keep shops grouping
105
+ }
86
106
 
87
107
  return {
88
108
  ...tool,