@payware/mcp-server 1.0.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.
- package/.env.example +92 -0
- package/MCP-SETUP.md +190 -0
- package/README.md +483 -0
- package/check-keys.js +52 -0
- package/keys/README.md +112 -0
- package/mcp-config.json +9 -0
- package/package.json +66 -0
- package/payware-mcp-config.json +12 -0
- package/src/auth/rsa-keys.js +82 -0
- package/src/auth/sandbox-setup.js +187 -0
- package/src/config/env.js +229 -0
- package/src/config/partner-types.js +143 -0
- package/src/core/auth/jwt-factory.js +182 -0
- package/src/core/auth/jwt-token.js +735 -0
- package/src/core/utils/json-serializer.js +100 -0
- package/src/core/utils/key-utils.js +133 -0
- package/src/index.js +237 -0
- package/src/isv/data/index.js +291 -0
- package/src/isv/deep-links/delete-product-link.js +154 -0
- package/src/isv/deep-links/delete-transaction-link.js +154 -0
- package/src/isv/deep-links/get-product-link.js +167 -0
- package/src/isv/deep-links/get-transaction-link.js +167 -0
- package/src/isv/deep-links/index.js +23 -0
- package/src/isv/index.js +103 -0
- package/src/isv/oauth2/create-token-simple.js +46 -0
- package/src/isv/oauth2/get-token-info.js +157 -0
- package/src/isv/oauth2/obtain-token.js +189 -0
- package/src/isv/oauth2/token-helper.js +151 -0
- package/src/isv/products/audios.js +470 -0
- package/src/isv/products/create-product.js +314 -0
- package/src/isv/products/delete-product.js +119 -0
- package/src/isv/products/get-product-image.js +247 -0
- package/src/isv/products/get-product.js +168 -0
- package/src/isv/products/index.js +65 -0
- package/src/isv/products/list-products.js +136 -0
- package/src/isv/products/schedules.js +522 -0
- package/src/isv/products/update-product.js +265 -0
- package/src/isv/transactions/cancel-transaction.js +129 -0
- package/src/isv/transactions/create-transaction.js +361 -0
- package/src/isv/transactions/get-status.js +217 -0
- package/src/isv/transactions/process-transaction.js +145 -0
- package/src/isv/transactions/simulate-callback.js +146 -0
- package/src/isv/transactions/transaction-history.js +186 -0
- package/src/isv/utils/auth.js +159 -0
- package/src/mcp-proxy.js +150 -0
- package/src/merchant/data/export-report.js +108 -0
- package/src/merchant/data/generate-report.js +151 -0
- package/src/merchant/data/get-report-status.js +120 -0
- package/src/merchant/data/index.js +351 -0
- package/src/merchant/deep-links/delete-product-link.js +121 -0
- package/src/merchant/deep-links/delete-transaction-link.js +121 -0
- package/src/merchant/deep-links/get-product-link.js +134 -0
- package/src/merchant/deep-links/get-transaction-link.js +134 -0
- package/src/merchant/deep-links/index.js +23 -0
- package/src/merchant/index.js +92 -0
- package/src/merchant/products/audios.js +606 -0
- package/src/merchant/products/create-product.js +288 -0
- package/src/merchant/products/delete-product.js +111 -0
- package/src/merchant/products/get-product-image.js +252 -0
- package/src/merchant/products/get-product.js +144 -0
- package/src/merchant/products/index.js +65 -0
- package/src/merchant/products/list-products.js +112 -0
- package/src/merchant/products/schedules.js +646 -0
- package/src/merchant/products/update-product.js +282 -0
- package/src/merchant/transactions/cancel-transaction.js +241 -0
- package/src/merchant/transactions/create-transaction.js +482 -0
- package/src/merchant/transactions/get-status.js +196 -0
- package/src/merchant/transactions/process-transaction.js +349 -0
- package/src/merchant/transactions/simulate-callback.js +246 -0
- package/src/merchant/transactions/transaction-history.js +258 -0
- package/src/payment-institution/data/index.js +280 -0
- package/src/payment-institution/deep-links/delete-transaction-link.js +128 -0
- package/src/payment-institution/deep-links/get-transaction-link.js +141 -0
- package/src/payment-institution/deep-links/index.js +17 -0
- package/src/payment-institution/index.js +92 -0
- package/src/payment-institution/transactions/create-pi-transaction.js +484 -0
- package/src/payment-institution/transactions/finalize-transaction.js +340 -0
- package/src/payment-institution/transactions/get-transaction-history.js +269 -0
- package/src/payment-institution/transactions/get-transaction-status.js +234 -0
- package/src/payment-institution/transactions/process-pi-transaction.js +421 -0
- package/src/payment-institution/transactions/simulate-pi-callback.js +326 -0
- package/src/payment-institution/transactions/soundbite-transaction.js +269 -0
- package/src/proxy-server.js +205 -0
- package/src/shared/data/data-api.js +372 -0
- package/src/shared/deep-links/api.js +214 -0
- package/src/shared/index.js +44 -0
- package/src/shared/products/products-api.js +872 -0
- package/src/tools/advanced-code-generator.js +2247 -0
- package/src/utils/code-examples/common/auth-examples.js +1482 -0
- package/src/utils/code-examples/common/helpers.js +416 -0
- package/src/utils/code-examples/common/utils-examples.js +591 -0
- package/src/utils/code-examples/frameworks/examples.js +859 -0
- package/src/utils/code-examples/index.js +441 -0
- package/src/utils/code-examples/isv/oauth2.js +600 -0
- package/src/utils/code-examples/isv/products.js +666 -0
- package/src/utils/code-examples/isv/transactions.js +813 -0
- package/src/utils/code-examples/merchant/products.js +497 -0
- package/src/utils/code-examples/merchant/transactions.js +623 -0
- package/src/utils/code-examples/payment-institution/transactions.js +388 -0
- package/src/utils/code-examples/shared/data.js +607 -0
- package/src/utils/code-examples/shared/deep-links.js +662 -0
- package/src/utils/code-examples/shared/soundbites.js +808 -0
- package/src/utils/code-examples.js +646 -0
- package/src/utils/format-request.js +363 -0
- package/src/utils/json-formatter.js +249 -0
- package/src/utils/server-info.js +132 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format and validate requests for payware API with deterministic JSON serialization
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createMinimizedJSON } from '../core/utils/json-serializer.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Format transaction request data
|
|
9
|
+
* @param {Object} data - Raw transaction data
|
|
10
|
+
* @returns {Object} Formatted and validated transaction data
|
|
11
|
+
*/
|
|
12
|
+
export function formatTransactionRequest(data) {
|
|
13
|
+
const {
|
|
14
|
+
type = 'PLAIN',
|
|
15
|
+
amount,
|
|
16
|
+
currency = 'EUR',
|
|
17
|
+
reasonL1,
|
|
18
|
+
reasonL2,
|
|
19
|
+
callbackUrl,
|
|
20
|
+
account,
|
|
21
|
+
friendlyName,
|
|
22
|
+
shop,
|
|
23
|
+
timeToLive = 120,
|
|
24
|
+
passbackParams
|
|
25
|
+
} = data;
|
|
26
|
+
|
|
27
|
+
// Validation
|
|
28
|
+
const errors = [];
|
|
29
|
+
|
|
30
|
+
// reasonL1 is required
|
|
31
|
+
if (!reasonL1 || typeof reasonL1 !== 'string' || reasonL1.trim().length === 0) {
|
|
32
|
+
errors.push('reasonL1 is required (transaction grounds description)');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (reasonL1 && reasonL1.length > 100) {
|
|
36
|
+
errors.push('reasonL1 cannot exceed 100 characters');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (reasonL2 && reasonL2.length > 100) {
|
|
40
|
+
errors.push('reasonL2 cannot exceed 100 characters');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Amount validation (optional for flexible amounts)
|
|
44
|
+
if (amount !== undefined && (typeof amount !== 'number' && typeof amount !== 'string')) {
|
|
45
|
+
errors.push('Amount must be a number or string representing currency value');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (amount !== undefined && parseFloat(amount) < 0) {
|
|
49
|
+
errors.push('Amount must be non-negative currency value');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Amount can be any positive value (limited by Java type implementation)
|
|
53
|
+
|
|
54
|
+
if (!['PLAIN', 'QR', 'BARCODE'].includes(type)) {
|
|
55
|
+
errors.push('Transaction type must be PLAIN, QR, or BARCODE');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Currency validation - accept any 3-character ISO code
|
|
59
|
+
if (!currency || typeof currency !== 'string' || !currency.match(/^[A-Z]{3}$/)) {
|
|
60
|
+
errors.push('Currency must be a valid ISO 3-character code (e.g., EUR, USD, JPY)');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (callbackUrl && !callbackUrl.match(/^https?:\/\/.+/)) {
|
|
64
|
+
errors.push('Callback URL must be a valid HTTP/HTTPS URL');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (account && account.length > 36) {
|
|
68
|
+
errors.push('Account identifier cannot exceed 36 characters');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (friendlyName && friendlyName.length > 100) {
|
|
72
|
+
errors.push('Friendly name cannot exceed 100 characters');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (shop && shop.length > 10) {
|
|
76
|
+
errors.push('Shop code cannot exceed 10 characters');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (timeToLive && (timeToLive < 60 || timeToLive > 600)) {
|
|
80
|
+
errors.push('Time to live must be between 60 and 600 seconds');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (errors.length > 0) {
|
|
84
|
+
throw new Error(`Validation errors: ${errors.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Format the request according to documentation structure
|
|
88
|
+
const formattedRequest = {
|
|
89
|
+
...(account && { account }),
|
|
90
|
+
...(friendlyName && { friendlyName }),
|
|
91
|
+
...(shop && { shop }),
|
|
92
|
+
...(callbackUrl && { callbackUrl }),
|
|
93
|
+
...(passbackParams && { passbackParams }),
|
|
94
|
+
trData: {
|
|
95
|
+
amount: amount !== undefined ? amount.toString() : '0.00',
|
|
96
|
+
currency,
|
|
97
|
+
reasonL1,
|
|
98
|
+
...(reasonL2 && { reasonL2 })
|
|
99
|
+
},
|
|
100
|
+
trOptions: {
|
|
101
|
+
type,
|
|
102
|
+
timeToLive
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
request: formattedRequest,
|
|
108
|
+
validation: {
|
|
109
|
+
valid: true,
|
|
110
|
+
errors: [],
|
|
111
|
+
warnings: []
|
|
112
|
+
},
|
|
113
|
+
formatted: true,
|
|
114
|
+
timestamp: new Date().toISOString()
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Format API headers
|
|
120
|
+
* @param {string} jwtToken - JWT authentication token
|
|
121
|
+
* @param {string} signature - Request signature (optional)
|
|
122
|
+
* @returns {Object} Formatted headers
|
|
123
|
+
*/
|
|
124
|
+
export function formatAPIHeaders(jwtToken, signature = null) {
|
|
125
|
+
const headers = {
|
|
126
|
+
'Authorization': `Bearer ${jwtToken}`,
|
|
127
|
+
'Content-Type': 'application/json',
|
|
128
|
+
'Api-Version': '1',
|
|
129
|
+
'Accept': 'application/json'
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (signature) {
|
|
133
|
+
headers['X-Signature'] = signature;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return headers;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Format curl command for API testing
|
|
141
|
+
* @param {string} method - HTTP method
|
|
142
|
+
* @param {string} endpoint - API endpoint
|
|
143
|
+
* @param {Object} headers - Request headers
|
|
144
|
+
* @param {Object} body - Request body (optional)
|
|
145
|
+
* @returns {string} Formatted curl command
|
|
146
|
+
*/
|
|
147
|
+
export function formatCurlCommand(method, endpoint, headers, body = null) {
|
|
148
|
+
let curl = `curl -X ${method.toUpperCase()} "${endpoint}"`;
|
|
149
|
+
|
|
150
|
+
// Add headers
|
|
151
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
152
|
+
curl += ` \\\n -H "${key}: ${value}"`;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Add body if present
|
|
156
|
+
if (body) {
|
|
157
|
+
curl += ` \\\n -d '${JSON.stringify(body, null, 2)}'`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return curl;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format request tool implementation
|
|
165
|
+
*/
|
|
166
|
+
export const formatRequestTool = {
|
|
167
|
+
name: "payware_utils_format_request",
|
|
168
|
+
description: "Format and validate payware API requests with proper structure and headers",
|
|
169
|
+
inputSchema: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
type: {
|
|
173
|
+
type: "string",
|
|
174
|
+
enum: ["transaction", "headers", "curl"],
|
|
175
|
+
description: "Type of formatting to perform"
|
|
176
|
+
},
|
|
177
|
+
data: {
|
|
178
|
+
type: "object",
|
|
179
|
+
description: "Data to format (structure depends on type)"
|
|
180
|
+
},
|
|
181
|
+
jwtToken: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "JWT token for header formatting"
|
|
184
|
+
},
|
|
185
|
+
signature: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Request signature (optional)"
|
|
188
|
+
},
|
|
189
|
+
endpoint: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description: "API endpoint URL (for curl formatting)"
|
|
192
|
+
},
|
|
193
|
+
method: {
|
|
194
|
+
type: "string",
|
|
195
|
+
enum: ["GET", "POST", "PUT", "DELETE"],
|
|
196
|
+
description: "HTTP method (for curl formatting)"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
required: ["type"],
|
|
200
|
+
additionalProperties: false
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
async handler(args) {
|
|
204
|
+
const { type, data = {}, jwtToken, signature, endpoint, method = 'POST' } = args;
|
|
205
|
+
|
|
206
|
+
switch (type) {
|
|
207
|
+
case 'transaction': {
|
|
208
|
+
if (!data.amount) {
|
|
209
|
+
throw new Error("Amount is required for transaction formatting");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const formatted = formatTransactionRequest(data);
|
|
213
|
+
|
|
214
|
+
const minimizedJSON = createMinimizedJSON(formatted.request);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
content: [{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: `๐ **Transaction Request Formatted**
|
|
220
|
+
|
|
221
|
+
**Formatted Request (Pretty):**
|
|
222
|
+
\`\`\`json
|
|
223
|
+
${JSON.stringify(formatted.request, null, 2)}
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
**Minimized JSON (API Format):**
|
|
227
|
+
\`\`\`json
|
|
228
|
+
${minimizedJSON}
|
|
229
|
+
\`\`\`
|
|
230
|
+
|
|
231
|
+
**Validation Result:**
|
|
232
|
+
- โ
Valid: ${formatted.validation.valid}
|
|
233
|
+
- Errors: ${formatted.validation.errors.length}
|
|
234
|
+
- Warnings: ${formatted.validation.warnings.length}
|
|
235
|
+
|
|
236
|
+
**Request Details:**
|
|
237
|
+
- Type: ${formatted.request.trOptions.type}
|
|
238
|
+
- Amount: ${formatted.request.trData.amount} ${formatted.request.trData.currency}
|
|
239
|
+
- Description: ${formatted.request.trData.reasonL1}
|
|
240
|
+
${formatted.request.trData.reasonL2 ? `- Description L2: ${formatted.request.trData.reasonL2}` : ''}
|
|
241
|
+
${formatted.request.callbackUrl ? `- Callback URL: ${formatted.request.callbackUrl}` : ''}
|
|
242
|
+
|
|
243
|
+
**Usage:**
|
|
244
|
+
Use the minimized JSON format for API calls to ensure MD5 consistency:
|
|
245
|
+
\`\`\`javascript
|
|
246
|
+
const response = await fetch('https://sandbox.payware.eu/api/transactions', {
|
|
247
|
+
method: 'POST',
|
|
248
|
+
headers: {
|
|
249
|
+
'Authorization': 'Bearer YOUR_JWT_TOKEN',
|
|
250
|
+
'Content-Type': 'application/json'
|
|
251
|
+
},
|
|
252
|
+
body: '${minimizedJSON}' // Use pre-minimized string for MD5 consistency
|
|
253
|
+
});
|
|
254
|
+
\`\`\`
|
|
255
|
+
|
|
256
|
+
**โ ๏ธ Important:** Always use the minimized JSON format to ensure the MD5 hash in your JWT token matches the request body.
|
|
257
|
+
|
|
258
|
+
**Formatted:** ${formatted.formatted}
|
|
259
|
+
**Timestamp:** ${formatted.timestamp}`
|
|
260
|
+
}]
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
case 'headers': {
|
|
265
|
+
if (!jwtToken) {
|
|
266
|
+
throw new Error("JWT token is required for header formatting");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const headers = formatAPIHeaders(jwtToken, signature);
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
content: [{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: `๐ **API Headers Formatted**
|
|
275
|
+
|
|
276
|
+
**Headers:**
|
|
277
|
+
\`\`\`json
|
|
278
|
+
${JSON.stringify(headers, null, 2)}
|
|
279
|
+
\`\`\`
|
|
280
|
+
|
|
281
|
+
**Header Breakdown:**
|
|
282
|
+
- **Authorization**: Bearer token for authentication
|
|
283
|
+
- **Content-Type**: JSON content type
|
|
284
|
+
- **Accept**: Expects JSON response
|
|
285
|
+
${signature ? '- **X-Signature**: Request signature for additional security' : ''}
|
|
286
|
+
|
|
287
|
+
**Usage in JavaScript:**
|
|
288
|
+
\`\`\`javascript
|
|
289
|
+
const headers = ${JSON.stringify(headers, null, 2)};
|
|
290
|
+
|
|
291
|
+
fetch(url, { headers, ...options });
|
|
292
|
+
\`\`\`
|
|
293
|
+
|
|
294
|
+
**Usage in Python:**
|
|
295
|
+
\`\`\`python
|
|
296
|
+
headers = ${JSON.stringify(headers, null, 2).replace(/"/g, "'")}
|
|
297
|
+
|
|
298
|
+
requests.post(url, headers=headers, json=data)
|
|
299
|
+
\`\`\`
|
|
300
|
+
|
|
301
|
+
**Usage in cURL:**
|
|
302
|
+
\`\`\`bash
|
|
303
|
+
${Object.entries(headers).map(([key, value]) => `curl -H "${key}: ${value}"`).join(' \\\n')} \\
|
|
304
|
+
https://sandbox.payware.eu/api/transactions
|
|
305
|
+
\`\`\``
|
|
306
|
+
}]
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
case 'curl': {
|
|
311
|
+
if (!endpoint) {
|
|
312
|
+
throw new Error("Endpoint is required for curl formatting");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!jwtToken) {
|
|
316
|
+
throw new Error("JWT token is required for curl formatting");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const headers = formatAPIHeaders(jwtToken, signature);
|
|
320
|
+
const curl = formatCurlCommand(method, endpoint, headers, data);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
content: [{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: `๐ป **cURL Command Generated**
|
|
326
|
+
|
|
327
|
+
**Command:**
|
|
328
|
+
\`\`\`bash
|
|
329
|
+
${curl}
|
|
330
|
+
\`\`\`
|
|
331
|
+
|
|
332
|
+
**Command Breakdown:**
|
|
333
|
+
- Method: ${method.toUpperCase()}
|
|
334
|
+
- Endpoint: ${endpoint}
|
|
335
|
+
- Headers: ${Object.keys(headers).length} headers included
|
|
336
|
+
${data && Object.keys(data).length > 0 ? `- Body: JSON payload included` : '- Body: No payload'}
|
|
337
|
+
|
|
338
|
+
**Usage:**
|
|
339
|
+
1. Copy the command above
|
|
340
|
+
2. Paste it into your terminal
|
|
341
|
+
3. Execute to test the API call directly
|
|
342
|
+
4. Modify parameters as needed
|
|
343
|
+
|
|
344
|
+
**Response:**
|
|
345
|
+
The command will return the API response directly in your terminal, including:
|
|
346
|
+
- HTTP status code
|
|
347
|
+
- Response headers
|
|
348
|
+
- JSON response body
|
|
349
|
+
- Any error messages
|
|
350
|
+
|
|
351
|
+
**Tips:**
|
|
352
|
+
- Add \`-v\` flag for verbose output with headers
|
|
353
|
+
- Add \`-i\` flag to include response headers
|
|
354
|
+
- Use \`-o filename\` to save response to file`
|
|
355
|
+
}]
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
default:
|
|
360
|
+
throw new Error(`Unknown formatting type: ${type}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON formatter utility for deterministic serialization
|
|
3
|
+
* Helps developers format JSON correctly for payware API requests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createDeterministicJSON, validateJSONConsistency } from '../core/utils/json-serializer.js';
|
|
7
|
+
import { generateContentMd5 } from '../core/auth/jwt-token.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format JSON with deterministic serialization tool
|
|
11
|
+
*/
|
|
12
|
+
export const formatJSONDeterministicTool = {
|
|
13
|
+
name: "payware_utils_format_json_deterministic",
|
|
14
|
+
description: "Format JSON payload with deterministic key sorting for consistent MD5 calculation and payware API compliance",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
payload: {
|
|
19
|
+
type: "object",
|
|
20
|
+
description: "JSON payload to format with deterministic serialization"
|
|
21
|
+
},
|
|
22
|
+
showComparison: {
|
|
23
|
+
type: "boolean",
|
|
24
|
+
description: "Show comparison between standard and deterministic JSON",
|
|
25
|
+
default: true
|
|
26
|
+
},
|
|
27
|
+
calculateMD5: {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
description: "Calculate MD5 hash for JWT contentMd5 usage",
|
|
30
|
+
default: true
|
|
31
|
+
},
|
|
32
|
+
validateConsistency: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Validate that multiple serializations produce the same result",
|
|
35
|
+
default: false
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
required: ["payload"],
|
|
39
|
+
additionalProperties: false
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async handler(args) {
|
|
43
|
+
const {
|
|
44
|
+
payload,
|
|
45
|
+
showComparison = true,
|
|
46
|
+
calculateMD5 = true,
|
|
47
|
+
validateConsistency = false
|
|
48
|
+
} = args;
|
|
49
|
+
|
|
50
|
+
if (!payload || typeof payload !== 'object') {
|
|
51
|
+
throw new Error("Payload must be a valid JSON object");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Generate different serializations
|
|
56
|
+
const standardJson = JSON.stringify(payload);
|
|
57
|
+
const prettyJson = JSON.stringify(payload, null, 2);
|
|
58
|
+
const deterministicJson = createDeterministicJSON(payload);
|
|
59
|
+
|
|
60
|
+
// Calculate MD5 hash
|
|
61
|
+
const md5Hash = calculateMD5 ? generateContentMd5(payload) : null;
|
|
62
|
+
|
|
63
|
+
// Get key ordering information
|
|
64
|
+
const originalKeys = Object.keys(payload);
|
|
65
|
+
const sortedKeys = [...originalKeys].sort();
|
|
66
|
+
const isAlreadySorted = JSON.stringify(originalKeys) === JSON.stringify(sortedKeys);
|
|
67
|
+
|
|
68
|
+
// Consistency validation
|
|
69
|
+
let consistencyTest = null;
|
|
70
|
+
if (validateConsistency) {
|
|
71
|
+
// Create multiple instances and test consistency
|
|
72
|
+
const testPayload1 = { ...payload };
|
|
73
|
+
const testPayload2 = JSON.parse(JSON.stringify(payload));
|
|
74
|
+
const testPayload3 = Object.assign({}, payload);
|
|
75
|
+
|
|
76
|
+
consistencyTest = {
|
|
77
|
+
test1: createDeterministicJSON(testPayload1),
|
|
78
|
+
test2: createDeterministicJSON(testPayload2),
|
|
79
|
+
test3: createDeterministicJSON(testPayload3),
|
|
80
|
+
allMatch: validateJSONConsistency(testPayload1, testPayload2) &&
|
|
81
|
+
validateJSONConsistency(testPayload2, testPayload3)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
content: [{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: `๐ **JSON Deterministic Formatter**
|
|
89
|
+
|
|
90
|
+
## Input Payload Analysis
|
|
91
|
+
|
|
92
|
+
**Original Key Order**: [${originalKeys.join(', ')}]
|
|
93
|
+
**Sorted Key Order**: [${sortedKeys.join(', ')}]
|
|
94
|
+
**Already Sorted**: ${isAlreadySorted ? 'โ
Yes' : 'โ No (will be reordered)'}
|
|
95
|
+
|
|
96
|
+
${showComparison ? `## JSON Serialization Comparison
|
|
97
|
+
|
|
98
|
+
**โ Standard JSON.stringify()** (property order varies):
|
|
99
|
+
\`\`\`json
|
|
100
|
+
${standardJson}
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
**๐ Pretty JSON** (formatted but inconsistent order):
|
|
104
|
+
\`\`\`json
|
|
105
|
+
${prettyJson}
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
**โ
Deterministic JSON** (sorted keys, consistent):
|
|
109
|
+
\`\`\`json
|
|
110
|
+
${deterministicJson}
|
|
111
|
+
\`\`\`
|
|
112
|
+
|
|
113
|
+
### Key Differences:
|
|
114
|
+
${isAlreadySorted ?
|
|
115
|
+
'- โ
Your object keys are already in sorted order' :
|
|
116
|
+
`- โ Keys will be reordered: ${originalKeys.join(' โ ')} becomes ${sortedKeys.join(' โ ')}`}
|
|
117
|
+
- โ
Deterministic JSON ensures consistent serialization across platforms
|
|
118
|
+
- โ
Required for accurate MD5 hash calculation in JWT tokens
|
|
119
|
+
` : ''}
|
|
120
|
+
|
|
121
|
+
## โ
**payware API Ready JSON**
|
|
122
|
+
|
|
123
|
+
Use this exact JSON string for both JWT MD5 calculation and HTTP request body:
|
|
124
|
+
|
|
125
|
+
\`\`\`json
|
|
126
|
+
${deterministicJson}
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
${calculateMD5 ? `## ๐ **MD5 Hash for JWT**
|
|
130
|
+
|
|
131
|
+
**contentMd5**: \`${md5Hash}\`
|
|
132
|
+
|
|
133
|
+
**Usage in JWT Header**:
|
|
134
|
+
\`\`\`json
|
|
135
|
+
{
|
|
136
|
+
"alg": "RS256",
|
|
137
|
+
"typ": "JWT",
|
|
138
|
+
"contentMd5": "${md5Hash}"
|
|
139
|
+
}
|
|
140
|
+
\`\`\`
|
|
141
|
+
` : ''}
|
|
142
|
+
|
|
143
|
+
## ๐ **Implementation Guide**
|
|
144
|
+
|
|
145
|
+
### Python Implementation:
|
|
146
|
+
\`\`\`python
|
|
147
|
+
import json
|
|
148
|
+
|
|
149
|
+
# CORRECT: Use deterministic JSON
|
|
150
|
+
json_body = json.dumps(payload, sort_keys=True, separators=(',', ':'))
|
|
151
|
+
jwt_token = create_jwt_token(json_body)
|
|
152
|
+
response = requests.post(url, data=json_body, headers=headers)
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
### Node.js Implementation:
|
|
156
|
+
\`\`\`javascript
|
|
157
|
+
// Helper function for deterministic JSON
|
|
158
|
+
function createDeterministicJSON(obj) {
|
|
159
|
+
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// CORRECT: Use deterministic JSON
|
|
163
|
+
const jsonBody = createDeterministicJSON(payload);
|
|
164
|
+
const jwtToken = createJWTToken(jsonBody);
|
|
165
|
+
const response = await axios.post(url, jsonBody, {headers});
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
### PHP Implementation:
|
|
169
|
+
\`\`\`php
|
|
170
|
+
// CORRECT: Use deterministic JSON
|
|
171
|
+
function createDeterministicJSON($obj) {
|
|
172
|
+
ksort($obj);
|
|
173
|
+
return json_encode($obj, JSON_UNESCAPED_SLASHES);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
$jsonBody = createDeterministicJSON($payload);
|
|
177
|
+
$jwtToken = createJWTToken($jsonBody);
|
|
178
|
+
// Use $jsonBody for both JWT and cURL request
|
|
179
|
+
\`\`\`
|
|
180
|
+
|
|
181
|
+
${consistencyTest ? `## ๐งช **Consistency Validation**
|
|
182
|
+
|
|
183
|
+
**Test Results**:
|
|
184
|
+
- Test 1: \`${consistencyTest.test1}\`
|
|
185
|
+
- Test 2: \`${consistencyTest.test2}\`
|
|
186
|
+
- Test 3: \`${consistencyTest.test3}\`
|
|
187
|
+
|
|
188
|
+
**All Match**: ${consistencyTest.allMatch ? 'โ
Consistent' : 'โ Inconsistent'}
|
|
189
|
+
|
|
190
|
+
${!consistencyTest.allMatch ? 'โ ๏ธ **Warning**: Inconsistent serialization detected. This could cause MD5 mismatches.' : ''}
|
|
191
|
+
` : ''}
|
|
192
|
+
|
|
193
|
+
## โ ๏ธ **PAYWARE Critical Requirements**
|
|
194
|
+
|
|
195
|
+
1. **Same String Requirement**: The EXACT same compact JSON string must be used for both JWT contentMd5 and HTTP request body
|
|
196
|
+
2. **Compact Format**: No extra whitespace (spaces, newlines) in JSON
|
|
197
|
+
3. **Consistent Serialization**: Use deterministic serialization to prevent variations
|
|
198
|
+
4. **MD5 Consistency**: Any difference will cause ERR_INVALID_MD5 authentication failures
|
|
199
|
+
|
|
200
|
+
Note: Sorted keys are our METHOD to guarantee requirement #1, not a payware requirement itself.
|
|
201
|
+
|
|
202
|
+
## ๐ **Next Steps**
|
|
203
|
+
|
|
204
|
+
1. Use the deterministic JSON above in your API request
|
|
205
|
+
2. Include the MD5 hash in your JWT header${calculateMD5 ? ` (\`${md5Hash}\`)` : ''}
|
|
206
|
+
3. Send the exact same JSON string in your HTTP request body
|
|
207
|
+
4. Verify with \`payware_authentication_validate_jwt\` tool
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
**Execution Info:**
|
|
211
|
+
- Tool: payware_utils_format_json_deterministic
|
|
212
|
+
- Keys Processed: ${originalKeys.length}
|
|
213
|
+
- Sorting Required: ${!isAlreadySorted}
|
|
214
|
+
- MD5 Calculated: ${calculateMD5}
|
|
215
|
+
- Executed: ${new Date().toISOString()}
|
|
216
|
+
- Status: โ
Complete`
|
|
217
|
+
}]
|
|
218
|
+
};
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
content: [{
|
|
222
|
+
type: "text",
|
|
223
|
+
text: `โ **JSON Formatting Failed**
|
|
224
|
+
|
|
225
|
+
**Error**: ${error.message}
|
|
226
|
+
|
|
227
|
+
**Common Issues**:
|
|
228
|
+
- Invalid JSON structure
|
|
229
|
+
- Circular references in payload
|
|
230
|
+
- Non-serializable values (functions, undefined, etc.)
|
|
231
|
+
- Different JSON strings used for JWT MD5 vs HTTP body
|
|
232
|
+
|
|
233
|
+
**Debug Steps for MD5 Errors**:
|
|
234
|
+
1. Verify the SAME string is used for JWT contentMd5 and HTTP body
|
|
235
|
+
2. Check for extra whitespace or formatting differences
|
|
236
|
+
3. Ensure consistent property ordering (use deterministic serialization)
|
|
237
|
+
4. Validate that objects are properly serializable
|
|
238
|
+
5. Test with the consistency validation option
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
**Execution Info:**
|
|
242
|
+
- Tool: payware_utils_format_json_deterministic
|
|
243
|
+
- Executed: ${new Date().toISOString()}
|
|
244
|
+
- Status: โ Error`
|
|
245
|
+
}]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
};
|