@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.
Files changed (106) hide show
  1. package/.env.example +92 -0
  2. package/MCP-SETUP.md +190 -0
  3. package/README.md +483 -0
  4. package/check-keys.js +52 -0
  5. package/keys/README.md +112 -0
  6. package/mcp-config.json +9 -0
  7. package/package.json +66 -0
  8. package/payware-mcp-config.json +12 -0
  9. package/src/auth/rsa-keys.js +82 -0
  10. package/src/auth/sandbox-setup.js +187 -0
  11. package/src/config/env.js +229 -0
  12. package/src/config/partner-types.js +143 -0
  13. package/src/core/auth/jwt-factory.js +182 -0
  14. package/src/core/auth/jwt-token.js +735 -0
  15. package/src/core/utils/json-serializer.js +100 -0
  16. package/src/core/utils/key-utils.js +133 -0
  17. package/src/index.js +237 -0
  18. package/src/isv/data/index.js +291 -0
  19. package/src/isv/deep-links/delete-product-link.js +154 -0
  20. package/src/isv/deep-links/delete-transaction-link.js +154 -0
  21. package/src/isv/deep-links/get-product-link.js +167 -0
  22. package/src/isv/deep-links/get-transaction-link.js +167 -0
  23. package/src/isv/deep-links/index.js +23 -0
  24. package/src/isv/index.js +103 -0
  25. package/src/isv/oauth2/create-token-simple.js +46 -0
  26. package/src/isv/oauth2/get-token-info.js +157 -0
  27. package/src/isv/oauth2/obtain-token.js +189 -0
  28. package/src/isv/oauth2/token-helper.js +151 -0
  29. package/src/isv/products/audios.js +470 -0
  30. package/src/isv/products/create-product.js +314 -0
  31. package/src/isv/products/delete-product.js +119 -0
  32. package/src/isv/products/get-product-image.js +247 -0
  33. package/src/isv/products/get-product.js +168 -0
  34. package/src/isv/products/index.js +65 -0
  35. package/src/isv/products/list-products.js +136 -0
  36. package/src/isv/products/schedules.js +522 -0
  37. package/src/isv/products/update-product.js +265 -0
  38. package/src/isv/transactions/cancel-transaction.js +129 -0
  39. package/src/isv/transactions/create-transaction.js +361 -0
  40. package/src/isv/transactions/get-status.js +217 -0
  41. package/src/isv/transactions/process-transaction.js +145 -0
  42. package/src/isv/transactions/simulate-callback.js +146 -0
  43. package/src/isv/transactions/transaction-history.js +186 -0
  44. package/src/isv/utils/auth.js +159 -0
  45. package/src/mcp-proxy.js +150 -0
  46. package/src/merchant/data/export-report.js +108 -0
  47. package/src/merchant/data/generate-report.js +151 -0
  48. package/src/merchant/data/get-report-status.js +120 -0
  49. package/src/merchant/data/index.js +351 -0
  50. package/src/merchant/deep-links/delete-product-link.js +121 -0
  51. package/src/merchant/deep-links/delete-transaction-link.js +121 -0
  52. package/src/merchant/deep-links/get-product-link.js +134 -0
  53. package/src/merchant/deep-links/get-transaction-link.js +134 -0
  54. package/src/merchant/deep-links/index.js +23 -0
  55. package/src/merchant/index.js +92 -0
  56. package/src/merchant/products/audios.js +606 -0
  57. package/src/merchant/products/create-product.js +288 -0
  58. package/src/merchant/products/delete-product.js +111 -0
  59. package/src/merchant/products/get-product-image.js +252 -0
  60. package/src/merchant/products/get-product.js +144 -0
  61. package/src/merchant/products/index.js +65 -0
  62. package/src/merchant/products/list-products.js +112 -0
  63. package/src/merchant/products/schedules.js +646 -0
  64. package/src/merchant/products/update-product.js +282 -0
  65. package/src/merchant/transactions/cancel-transaction.js +241 -0
  66. package/src/merchant/transactions/create-transaction.js +482 -0
  67. package/src/merchant/transactions/get-status.js +196 -0
  68. package/src/merchant/transactions/process-transaction.js +349 -0
  69. package/src/merchant/transactions/simulate-callback.js +246 -0
  70. package/src/merchant/transactions/transaction-history.js +258 -0
  71. package/src/payment-institution/data/index.js +280 -0
  72. package/src/payment-institution/deep-links/delete-transaction-link.js +128 -0
  73. package/src/payment-institution/deep-links/get-transaction-link.js +141 -0
  74. package/src/payment-institution/deep-links/index.js +17 -0
  75. package/src/payment-institution/index.js +92 -0
  76. package/src/payment-institution/transactions/create-pi-transaction.js +484 -0
  77. package/src/payment-institution/transactions/finalize-transaction.js +340 -0
  78. package/src/payment-institution/transactions/get-transaction-history.js +269 -0
  79. package/src/payment-institution/transactions/get-transaction-status.js +234 -0
  80. package/src/payment-institution/transactions/process-pi-transaction.js +421 -0
  81. package/src/payment-institution/transactions/simulate-pi-callback.js +326 -0
  82. package/src/payment-institution/transactions/soundbite-transaction.js +269 -0
  83. package/src/proxy-server.js +205 -0
  84. package/src/shared/data/data-api.js +372 -0
  85. package/src/shared/deep-links/api.js +214 -0
  86. package/src/shared/index.js +44 -0
  87. package/src/shared/products/products-api.js +872 -0
  88. package/src/tools/advanced-code-generator.js +2247 -0
  89. package/src/utils/code-examples/common/auth-examples.js +1482 -0
  90. package/src/utils/code-examples/common/helpers.js +416 -0
  91. package/src/utils/code-examples/common/utils-examples.js +591 -0
  92. package/src/utils/code-examples/frameworks/examples.js +859 -0
  93. package/src/utils/code-examples/index.js +441 -0
  94. package/src/utils/code-examples/isv/oauth2.js +600 -0
  95. package/src/utils/code-examples/isv/products.js +666 -0
  96. package/src/utils/code-examples/isv/transactions.js +813 -0
  97. package/src/utils/code-examples/merchant/products.js +497 -0
  98. package/src/utils/code-examples/merchant/transactions.js +623 -0
  99. package/src/utils/code-examples/payment-institution/transactions.js +388 -0
  100. package/src/utils/code-examples/shared/data.js +607 -0
  101. package/src/utils/code-examples/shared/deep-links.js +662 -0
  102. package/src/utils/code-examples/shared/soundbites.js +808 -0
  103. package/src/utils/code-examples.js +646 -0
  104. package/src/utils/format-request.js +363 -0
  105. package/src/utils/json-formatter.js +249 -0
  106. package/src/utils/server-info.js +132 -0
@@ -0,0 +1,735 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import crypto from 'crypto';
3
+ import { normalizePrivateKey, getKeyInfo } from '../utils/key-utils.js';
4
+ import { getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
5
+ import { createDeterministicJSON } from '../utils/json-serializer.js';
6
+
7
+ /**
8
+ * Generate MD5 hash for JWT contentMd5 header
9
+ *
10
+ * PAYWARE REQUIREMENT: The MD5 hash must be calculated from the EXACT same
11
+ * compact JSON string that will be sent as the HTTP request body.
12
+ *
13
+ * CRITICAL: Any difference in whitespace, property order, or formatting
14
+ * between the string used for MD5 and the HTTP body will cause authentication
15
+ * failures with ERR_INVALID_MD5 errors.
16
+ *
17
+ * @param {Object|string} body - Request body (object will be consistently serialized)
18
+ * @returns {string} Base64 encoded MD5 hash for JWT contentMd5 header
19
+ */
20
+ export function generateContentMd5(body) {
21
+ if (!body) return null;
22
+
23
+ // CRITICAL: Use deterministic JSON serialization with sorted keys
24
+ // This ensures consistent MD5 hashes regardless of property order
25
+ const bodyString = typeof body === 'string' ? body : createDeterministicJSON(body);
26
+
27
+ // Generate MD5 hash as raw binary and encode in base64
28
+ const hash = crypto.createHash('md5');
29
+ hash.update(bodyString, 'utf8');
30
+ return hash.digest('base64');
31
+ }
32
+
33
+ /**
34
+ * Generate the exact JSON string for both JWT MD5 and HTTP request body
35
+ *
36
+ * PAYWARE REQUIREMENT: The same compact JSON string must be used for:
37
+ * 1. JWT contentMd5 calculation (passed to generateContentMd5)
38
+ * 2. HTTP request body (sent to payware API)
39
+ *
40
+ * This function guarantees consistency by using deterministic serialization.
41
+ * Any mismatch between these two uses will result in authentication failure.
42
+ *
43
+ * @param {Object} payload - Request payload object
44
+ * @returns {string} Compact JSON string for both MD5 and HTTP body
45
+ */
46
+ export function serializePayload(payload) {
47
+ if (!payload) return null;
48
+ if (typeof payload === 'string') return payload;
49
+
50
+ // Use deterministic JSON serialization with sorted keys
51
+ // This ensures consistent serialization regardless of property order
52
+ return createDeterministicJSON(payload);
53
+ }
54
+
55
+ /**
56
+ * Create JWT token for payware API authentication according to documentation
57
+ * @param {string} partnerId - Partner ID from payware
58
+ * @param {string} privateKey - RSA private key in PEM format
59
+ * @param {Object} requestBody - Request body (for POST/PUT/PATCH requests)
60
+ * @returns {Object} Object containing JWT token and metadata
61
+ */
62
+ export function createJWTToken(partnerId, privateKey, requestBody = null) {
63
+ const now = Math.floor(Date.now() / 1000);
64
+
65
+ // Normalize the private key to handle flexible formats
66
+ const normalizedKey = normalizePrivateKey(privateKey);
67
+
68
+ // JWT Header (as per documentation)
69
+ const header = {
70
+ alg: 'RS256',
71
+ typ: 'JWT'
72
+ };
73
+
74
+ // Add contentMd5 to header if request body exists (for POST/PUT/PATCH)
75
+ // CRITICAL: The requestBody here must be the EXACT same string that will
76
+ // be sent as the HTTP request body, otherwise MD5 will not match
77
+ if (requestBody) {
78
+ header.contentMd5 = generateContentMd5(requestBody);
79
+ }
80
+
81
+ // JWT Payload (as per documentation)
82
+ const payload = {
83
+ iss: partnerId,
84
+ aud: 'https://payware.eu', // Required by documentation
85
+ iat: now
86
+ };
87
+
88
+ const token = jwt.sign(payload, normalizedKey, {
89
+ algorithm: 'RS256',
90
+ header: header
91
+ });
92
+
93
+ return {
94
+ token,
95
+ partnerId,
96
+ audience: 'https://payware.eu',
97
+ issuedAt: new Date(now * 1000).toISOString(),
98
+ contentMd5: header.contentMd5 || null,
99
+ hasBody: !!requestBody
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Create request signature for payware API (legacy - may not be needed with proper JWT)
105
+ * @param {string} method - HTTP method (GET, POST, etc.)
106
+ * @param {string} path - API path
107
+ * @param {Object} body - Request body (for POST requests)
108
+ * @param {string} privateKey - RSA private key in PEM format
109
+ * @returns {string} Base64 encoded signature
110
+ */
111
+ export function createRequestSignature(method, path, body, privateKey) {
112
+ const bodyString = body ? JSON.stringify(body) : '';
113
+ const stringToSign = `${method.toUpperCase()}${path}${bodyString}`;
114
+
115
+ const sign = crypto.createSign('RSA-SHA256');
116
+ sign.update(stringToSign);
117
+ sign.end();
118
+
119
+ const signature = sign.sign(privateKey, 'base64');
120
+ return signature;
121
+ }
122
+
123
+ /**
124
+ * Validate and debug JWT token implementation
125
+ */
126
+ export const validateJWTTokenTool = {
127
+ name: "payware_authentication_validate_jwt",
128
+ description: "Validate and debug JWT token structure, decode payload/headers, verify MD5 calculation, and check RS256 signature format for payware API compliance",
129
+ inputSchema: {
130
+ type: "object",
131
+ properties: {
132
+ jwtToken: {
133
+ type: "string",
134
+ description: "JWT token to validate and debug"
135
+ },
136
+ expectedPayload: {
137
+ type: "object",
138
+ description: "Expected request body (optional - for MD5 validation)"
139
+ }
140
+ },
141
+ required: ["jwtToken"],
142
+ additionalProperties: false
143
+ },
144
+
145
+ async handler(args) {
146
+ const { jwtToken, expectedPayload } = args;
147
+
148
+ if (!jwtToken) {
149
+ throw new Error("JWT token is required");
150
+ }
151
+
152
+ try {
153
+ // Parse JWT token without verification first
154
+ const decoded = jwt.decode(jwtToken, { complete: true });
155
+
156
+ if (!decoded) {
157
+ throw new Error("Invalid JWT token format");
158
+ }
159
+
160
+ const { header, payload } = decoded;
161
+
162
+ // Validation results
163
+ const validation = {
164
+ structure: true,
165
+ algorithm: header.alg === 'RS256',
166
+ type: header.typ === 'JWT',
167
+ audience: payload.aud === 'https://payware.eu',
168
+ issuer: !!payload.iss,
169
+ issuedAt: !!payload.iat,
170
+ contentMd5: header.contentMd5 ? true : false
171
+ };
172
+
173
+ // MD5 validation if expected payload provided
174
+ let md5Validation = null;
175
+ if (expectedPayload && header.contentMd5) {
176
+ const calculatedMd5 = generateContentMd5(expectedPayload);
177
+ md5Validation = {
178
+ provided: header.contentMd5,
179
+ calculated: calculatedMd5,
180
+ matches: header.contentMd5 === calculatedMd5,
181
+ deterministicJson: createDeterministicJSON(expectedPayload)
182
+ };
183
+ }
184
+
185
+ // Check for common issues
186
+ const issues = [];
187
+ if (!validation.algorithm) issues.push("Algorithm should be 'RS256'");
188
+ if (!validation.type) issues.push("Type should be 'JWT'");
189
+ if (!validation.audience) issues.push("Audience should be 'https://payware.eu'");
190
+ if (!validation.issuer) issues.push("Missing issuer (iss) claim");
191
+ if (!validation.issuedAt) issues.push("Missing issued at (iat) claim");
192
+ if (md5Validation && !md5Validation.matches) {
193
+ issues.push("MD5 hash mismatch - ensure deterministic JSON serialization with sorted keys");
194
+ }
195
+
196
+ const overallValid = Object.values(validation).every(v => v) && (!md5Validation || md5Validation.matches);
197
+
198
+ return {
199
+ content: [{
200
+ type: "text",
201
+ text: `🔍 **JWT Token Validation Report**
202
+
203
+ ${overallValid ? '✅ **VALID JWT TOKEN**' : '❌ **INVALID JWT TOKEN**'}
204
+
205
+ ## JWT Structure Analysis
206
+
207
+ **Header:**
208
+ \`\`\`json
209
+ ${JSON.stringify(header, null, 2)}
210
+ \`\`\`
211
+
212
+ **Payload:**
213
+ \`\`\`json
214
+ ${JSON.stringify(payload, null, 2)}
215
+ \`\`\`
216
+
217
+ ## Validation Results
218
+
219
+ ${validation.structure ? '✅' : '❌'} **Structure**: JWT has valid 3-part structure
220
+ ${validation.algorithm ? '✅' : '❌'} **Algorithm**: RS256 ${validation.algorithm ? '(correct)' : `(found: ${header.alg})`}
221
+ ${validation.type ? '✅' : '❌'} **Type**: JWT ${validation.type ? '(correct)' : `(found: ${header.typ})`}
222
+ ${validation.audience ? '✅' : '❌'} **Audience**: https://payware.eu ${validation.audience ? '(correct)' : `(found: ${payload.aud})`}
223
+ ${validation.issuer ? '✅' : '❌'} **Issuer**: ${payload.iss || 'Missing'}
224
+ ${validation.issuedAt ? '✅' : '❌'} **Issued At**: ${payload.iat ? new Date(payload.iat * 1000).toISOString() : 'Missing'}
225
+ ${validation.contentMd5 ? '✅' : 'â„šī¸'} **Content MD5**: ${header.contentMd5 || 'Not present (OK for GET requests)'}
226
+
227
+ ${md5Validation ? `## MD5 Hash Validation
228
+
229
+ ${md5Validation.matches ? '✅' : '❌'} **MD5 Match**: ${md5Validation.matches ? 'Correct' : 'Mismatch detected'}
230
+ **Provided**: \`${md5Validation.provided}\`
231
+ **Calculated**: \`${md5Validation.calculated}\`
232
+
233
+ **Deterministic JSON Used**:
234
+ \`\`\`json
235
+ ${md5Validation.deterministicJson}
236
+ \`\`\`
237
+
238
+ ${!md5Validation.matches ? `
239
+ âš ī¸ **MD5 Mismatch Debugging**:
240
+
241
+ PAYWARE REQUIREMENT: The EXACT same compact JSON string must be used for both JWT contentMd5 and HTTP body.
242
+
243
+ **Common Causes**:
244
+ 1. Different JSON strings used for JWT MD5 vs HTTP body
245
+ 2. Extra whitespace or formatting differences
246
+ 3. Property order variations between serializations
247
+
248
+ **Solution**: Use deterministic JSON serialization to guarantee consistency
249
+
250
+ **Correct Implementation Pattern**:
251
+ \`\`\`python
252
+ # Use deterministic JSON serialization
253
+ json_body = json.dumps(payload, sort_keys=True, separators=(',', ':'))
254
+ jwt_token = create_jwt_token(json_body)
255
+ response = requests.post(url, data=json_body, headers=headers)
256
+ \`\`\`
257
+ ` : ''}
258
+ ` : ''}
259
+
260
+ ${issues.length > 0 ? `## âš ī¸ Issues Found
261
+
262
+ ${issues.map(issue => `- ${issue}`).join('\n')}
263
+
264
+ ## Recommendations
265
+
266
+ ${issues.includes("Algorithm should be 'RS256'") ? '- Update JWT algorithm to RS256\n' : ''}
267
+ ${issues.includes("Type should be 'JWT'") ? '- Set JWT type to "JWT"\n' : ''}
268
+ ${issues.includes("Audience should be 'https://payware.eu'") ? '- Set audience to "https://payware.eu" (not just "payware")\n' : ''}
269
+ ${issues.includes("Missing issuer (iss) claim") ? '- Add your partner ID as the issuer claim\n' : ''}
270
+ ${issues.includes("Missing issued at (iat) claim") ? '- Add current Unix timestamp as issued at claim\n' : ''}
271
+ ${issues.some(i => i.includes('MD5 hash mismatch')) ? '- Use deterministic JSON serialization with sorted keys\n- Ensure same JSON string for MD5 and HTTP body\n' : ''}
272
+ ` : '## ✅ No Issues Found\n\nYour JWT token is properly formatted for payware API usage.'}
273
+
274
+ ## Technical Details
275
+
276
+ **Token Length**: ${jwtToken.length} characters
277
+ **Validation Timestamp**: ${new Date().toISOString()}
278
+ **payware API Compliance**: ${overallValid ? 'PASS' : 'FAIL'}
279
+
280
+ ---
281
+ **Execution Info:**
282
+ - Tool: payware_authentication_validate_jwt
283
+ - Executed: ${new Date().toISOString()}
284
+ - Status: ${overallValid ? '✅ Valid' : '❌ Invalid'}`
285
+ }]
286
+ };
287
+ } catch (error) {
288
+ return {
289
+ content: [{
290
+ type: "text",
291
+ text: `❌ **JWT Validation Failed**
292
+
293
+ **Error**: ${error.message}
294
+
295
+ ## Common JWT Issues
296
+
297
+ 1. **Invalid Format**: JWT should have 3 parts separated by dots (header.payload.signature)
298
+ 2. **Invalid Base64**: JWT parts must be valid Base64 encoded
299
+ 3. **Malformed JSON**: Header and payload must be valid JSON
300
+ 4. **Missing Components**: Ensure all required claims are present
301
+
302
+ ## Debug Steps
303
+
304
+ 1. Check that your JWT has exactly 2 dots separating 3 parts
305
+ 2. Verify each part is valid Base64
306
+ 3. Decode header and payload manually to check JSON structure
307
+ 4. Ensure all required claims (iss, aud, iat) are present
308
+
309
+ **Token Provided**: ${jwtToken.substring(0, 50)}...
310
+
311
+ ---
312
+ **Execution Info:**
313
+ - Tool: payware_authentication_validate_jwt
314
+ - Executed: ${new Date().toISOString()}
315
+ - Status: ❌ Error`
316
+ }]
317
+ };
318
+ }
319
+ }
320
+ };
321
+
322
+ /**
323
+ * Test JWT token creation with step-by-step breakdown
324
+ */
325
+ export const testJWTTokenTool = {
326
+ name: "payware_authentication_test_jwt",
327
+ description: "Test JWT token creation process with detailed step-by-step breakdown, showing deterministic JSON serialization, MD5 calculation, and final JWT structure",
328
+ inputSchema: {
329
+ type: "object",
330
+ properties: {
331
+ partnerId: {
332
+ type: "string",
333
+ description: "Partner ID for JWT creation (defaults to environment)"
334
+ },
335
+ requestPayload: {
336
+ type: "object",
337
+ description: "Sample request payload to demonstrate MD5 calculation"
338
+ },
339
+ showPrivateKey: {
340
+ type: "boolean",
341
+ description: "Whether to show private key details (for debugging)",
342
+ default: false
343
+ }
344
+ },
345
+ additionalProperties: false
346
+ },
347
+
348
+ async handler(args) {
349
+ const {
350
+ partnerId = getPartnerIdSafe(),
351
+ requestPayload = { amount: "25.50", currency: "EUR", reasonL1: "Test payment" },
352
+ showPrivateKey = false
353
+ } = args;
354
+
355
+ if (!partnerId) {
356
+ throw new Error("Partner ID is required. Set PAYWARE_PARTNER_ID environment variable or provide as parameter.");
357
+ }
358
+
359
+ const privateKey = getPrivateKeySafe();
360
+ if (!privateKey) {
361
+ throw new Error("Private key is required. Set PAYWARE_SANDBOX_PRIVATE_KEY_PATH environment variable.");
362
+ }
363
+
364
+ try {
365
+ const steps = [];
366
+
367
+ // Step 1: JSON Serialization
368
+ const originalJson = JSON.stringify(requestPayload);
369
+ const deterministicJson = createDeterministicJSON(requestPayload);
370
+ const sortedKeys = Object.keys(requestPayload).sort();
371
+
372
+ steps.push(`**Step 1: JSON Serialization**
373
+ Input payload:
374
+ \`\`\`json
375
+ ${JSON.stringify(requestPayload, null, 2)}
376
+ \`\`\`
377
+
378
+ ❌ **Standard JSON.stringify()** (property order varies):
379
+ \`\`\`json
380
+ ${originalJson}
381
+ \`\`\`
382
+
383
+ ✅ **Deterministic JSON** (sorted keys):
384
+ \`\`\`json
385
+ ${deterministicJson}
386
+ \`\`\`
387
+
388
+ **Key Sorting**: [${sortedKeys.join(', ')}]`);
389
+
390
+ // Step 2: MD5 Calculation
391
+ const md5Hash = generateContentMd5(requestPayload);
392
+
393
+ steps.push(`**Step 2: MD5 Hash Calculation**
394
+ Using deterministic JSON string:
395
+ \`\`\`
396
+ ${deterministicJson}
397
+ \`\`\`
398
+
399
+ MD5 hash (Base64): \`${md5Hash}\`
400
+
401
+ **Process**:
402
+ 1. Create MD5 hash from UTF-8 bytes of deterministic JSON
403
+ 2. Encode hash as Base64
404
+ 3. Include in JWT header as \`contentMd5\``);
405
+
406
+ // Step 3: JWT Header Construction
407
+ const jwtHeader = {
408
+ alg: 'RS256',
409
+ typ: 'JWT',
410
+ contentMd5: md5Hash
411
+ };
412
+
413
+ steps.push(`**Step 3: JWT Header Construction**
414
+ \`\`\`json
415
+ ${JSON.stringify(jwtHeader, null, 2)}
416
+ \`\`\`
417
+
418
+ **Requirements**:
419
+ - Algorithm: RS256 (RSA with SHA-256)
420
+ - Type: JWT
421
+ - Content MD5: ${md5Hash}`);
422
+
423
+ // Step 4: JWT Payload Construction
424
+ const now = Math.floor(Date.now() / 1000);
425
+ const jwtPayload = {
426
+ iss: partnerId,
427
+ aud: 'https://payware.eu',
428
+ iat: now
429
+ };
430
+
431
+ steps.push(`**Step 4: JWT Payload Construction**
432
+ \`\`\`json
433
+ ${JSON.stringify(jwtPayload, null, 2)}
434
+ \`\`\`
435
+
436
+ **Requirements**:
437
+ - Issuer (iss): Your partner ID
438
+ - Audience (aud): **MUST be https://payware.eu** (not just "payware")
439
+ - Issued At (iat): Unix timestamp (${new Date(now * 1000).toISOString()})`);
440
+
441
+ // Step 5: JWT Token Creation
442
+ const finalToken = createJWTToken(partnerId, privateKey, requestPayload);
443
+
444
+ steps.push(`**Step 5: JWT Token Creation**
445
+ Final JWT token:
446
+ \`\`\`
447
+ ${finalToken.token}
448
+ \`\`\`
449
+
450
+ **Token Components**:
451
+ - Header (Base64): \`${finalToken.token.split('.')[0]}\`
452
+ - Payload (Base64): \`${finalToken.token.split('.')[1]}\`
453
+ - Signature (Base64): \`${finalToken.token.split('.')[2].substring(0, 20)}...\`
454
+
455
+ **Length**: ${finalToken.token.length} characters`);
456
+
457
+ // Step 6: Private Key Information
458
+ const keyInfo = getKeyInfo(privateKey);
459
+ const keyDetails = showPrivateKey ?
460
+ `**Private Key Details**:
461
+ - Format: ${keyInfo.format}
462
+ - Has Headers: ${keyInfo.hasHeaders}
463
+ - Length: ${keyInfo.length} characters
464
+ - Type: ${keyInfo.isPrivate ? 'Private' : 'Unknown'}` :
465
+ `**Private Key**: Available (${keyInfo.format}, ${keyInfo.length} chars)
466
+ *Use \`showPrivateKey: true\` for more details*`;
467
+
468
+ steps.push(`**Step 6: Signature Creation**
469
+ ${keyDetails}
470
+
471
+ **Signature Process**:
472
+ 1. Combine Base64(header) + "." + Base64(payload)
473
+ 2. Sign with RSA private key using SHA-256
474
+ 3. Encode signature as Base64
475
+ 4. Final JWT: header.payload.signature`);
476
+
477
+ return {
478
+ content: [{
479
+ type: "text",
480
+ text: `đŸ§Ē **JWT Token Creation Test - Step by Step**
481
+
482
+ ${steps.join('\n\n---\n\n')}
483
+
484
+ ## ✅ **Complete JWT Ready for API Use**
485
+
486
+ **Headers for API Request**:
487
+ \`\`\`
488
+ Authorization: Bearer ${finalToken.token}
489
+ Content-Type: application/json
490
+ Api-Version: 1
491
+ \`\`\`
492
+
493
+ **HTTP Request Body** (must match deterministic JSON):
494
+ \`\`\`json
495
+ ${deterministicJson}
496
+ \`\`\`
497
+
498
+ ## 🔑 **Key Insights**
499
+
500
+ 1. **Deterministic JSON is Critical**: Property order MUST be sorted
501
+ 2. **MD5 Consistency**: Same JSON string for both MD5 and HTTP body
502
+ 3. **Audience Requirement**: Must be "https://payware.eu" (include https://)
503
+ 4. **RS256 Algorithm**: RSA with SHA-256 signature
504
+ 5. **Content MD5 in Header**: Not in payload, but in JWT header
505
+
506
+ ## 🐛 **Common Mistakes to Avoid**
507
+
508
+ ❌ Using \`JSON.stringify()\` without sorted keys
509
+ ❌ Different JSON for MD5 vs HTTP request
510
+ ❌ Setting audience to "payware" instead of "https://payware.eu"
511
+ ❌ Putting contentMd5 in payload instead of header
512
+ ❌ Using wrong algorithm (HS256 instead of RS256)
513
+
514
+ ---
515
+ **Execution Info:**
516
+ - Tool: payware_authentication_test_jwt
517
+ - Partner ID: ${partnerId}
518
+ - Executed: ${new Date().toISOString()}
519
+ - Status: ✅ Complete`
520
+ }]
521
+ };
522
+ } catch (error) {
523
+ return {
524
+ content: [{
525
+ type: "text",
526
+ text: `❌ **JWT Testing Failed**
527
+
528
+ **Error**: ${error.message}
529
+
530
+ **Common Issues**:
531
+ - Missing environment variables (PAYWARE_PARTNER_ID, PAYWARE_SANDBOX_PRIVATE_KEY_PATH)
532
+ - Invalid private key format
533
+ - Permission issues reading private key file
534
+
535
+ **Debug Steps**:
536
+ 1. Check that environment variables are set
537
+ 2. Verify private key file exists and is readable
538
+ 3. Ensure private key is in proper PEM format
539
+
540
+ ---
541
+ **Execution Info:**
542
+ - Tool: payware_authentication_test_jwt
543
+ - Executed: ${new Date().toISOString()}
544
+ - Status: ❌ Error`
545
+ }]
546
+ };
547
+ }
548
+ }
549
+ };
550
+
551
+ /**
552
+ * Create JWT token tool implementation
553
+ */
554
+ export const createJWTTokenTool = {
555
+ name: "payware_authentication_create_jwt_token",
556
+ description: "Create JWT token for payware API authentication according to official documentation",
557
+ inputSchema: {
558
+ type: "object",
559
+ properties: {
560
+ partnerId: {
561
+ type: "string",
562
+ description: "Partner ID from payware dashboard. Uses PAYWARE_PARTNER_ID env var as default."
563
+ },
564
+ privateKey: {
565
+ type: "string",
566
+ description: "RSA private key - accepts PEM format with/without headers or raw base64 content. Uses environment-specific private key as default."
567
+ },
568
+ requestBody: {
569
+ type: "object",
570
+ description: "Request body (for POST/PUT/PATCH requests that need contentMd5)"
571
+ }
572
+ },
573
+ additionalProperties: false
574
+ },
575
+
576
+ async handler(args) {
577
+ const {
578
+ partnerId = getPartnerIdSafe(),
579
+ privateKey = getPrivateKeySafe(),
580
+ requestBody
581
+ } = args;
582
+
583
+ if (!partnerId) {
584
+ throw new Error("Partner ID is required. Provide via 'partnerId' parameter or set PAYWARE_PARTNER_ID environment variable.");
585
+ }
586
+
587
+ if (!privateKey) {
588
+ throw new Error("Private key is required. Provide via 'privateKey' parameter or set environment-specific private key variable.");
589
+ }
590
+
591
+ // Get key info for validation and user feedback
592
+ const keyInfo = getKeyInfo(privateKey);
593
+
594
+ try {
595
+ const tokenData = createJWTToken(partnerId, privateKey, requestBody);
596
+
597
+ // Add key format information to the response
598
+ const keyFormatInfo = keyInfo.hasHeaders
599
+ ? `✅ Standard PEM format (${keyInfo.format})`
600
+ : `🔧 Normalized from base64 content to PEM format`;
601
+
602
+ const originalResponse = tokenData;
603
+
604
+ return {
605
+ content: [{
606
+ type: "text",
607
+ text: `🔑 **JWT Token Created Successfully (payware Specification)**
608
+
609
+ **🔧 Key Format:** ${keyFormatInfo}
610
+
611
+ **Token:**
612
+ \`\`\`
613
+ ${tokenData.token}
614
+ \`\`\`
615
+
616
+ **Token Details:**
617
+ - Partner ID (iss): ${tokenData.partnerId}
618
+ - Audience (aud): ${tokenData.audience}
619
+ - Issued At (iat): ${tokenData.issuedAt}
620
+ - Algorithm: RS256
621
+ - Type: JWT
622
+ ${tokenData.contentMd5 ? `- Content MD5: ${tokenData.contentMd5}` : '- Content MD5: Not included (no request body)'}
623
+
624
+ **JWT Structure (Decoded):**
625
+ **Header:**
626
+ \`\`\`json
627
+ {
628
+ "alg": "RS256",
629
+ "typ": "JWT"${tokenData.contentMd5 ? `,\n "contentMd5": "${tokenData.contentMd5}"` : ''}
630
+ }
631
+ \`\`\`
632
+
633
+ **Payload:**
634
+ \`\`\`json
635
+ {
636
+ "iss": "${tokenData.partnerId}",
637
+ "aud": "https://payware.eu",
638
+ "iat": ${Math.floor(Date.parse(tokenData.issuedAt) / 1000)}
639
+ }
640
+ \`\`\`
641
+
642
+ **Usage:**
643
+ Add these headers to your API requests:
644
+ \`\`\`
645
+ Authorization: Bearer ${tokenData.token}
646
+ Api-Version: 1
647
+ Content-Type: application/json
648
+ \`\`\`
649
+
650
+ **✅ Complies with payware Documentation:**
651
+ - ✅ Algorithm: RS256
652
+ - ✅ Type: JWT
653
+ - ✅ Issuer: Your Partner ID
654
+ - ✅ Audience: https://payware.eu
655
+ - ✅ Issued At: Unix timestamp
656
+ ${tokenData.contentMd5 ? '- ✅ Content MD5: Included for request body' : '- â„šī¸ Content MD5: Not needed for GET requests'}
657
+
658
+ **📝 Private Key Formats Supported:**
659
+ - ✅ Full PEM format with headers/footers
660
+ - ✅ Base64 content only (headers added automatically)
661
+ - ✅ Mixed formats (normalized automatically)
662
+
663
+ ## âš ī¸ **CRITICAL AUTHENTICATION REQUIREMENTS**
664
+
665
+ ### JSON Body Consistency Rule
666
+ ${tokenData.contentMd5 ? `**Your contentMd5 (${tokenData.contentMd5}) MUST match the exact bytes sent in HTTP request.**
667
+
668
+ ❌ **Common Error Causing ERR_INVALID_MD5:**
669
+ \`\`\`python
670
+ # Wrong - different serialization formats!
671
+ jwt_token = create_jwt(json.dumps(payload)) # One format
672
+ requests.post(url, json=payload) # Different format!
673
+ \`\`\`
674
+
675
+ ✅ **Correct Implementation:**
676
+ \`\`\`python
677
+ # Same serialization for both MD5 and HTTP body
678
+ json_body = json.dumps(payload, separators=(',', ':')) # Compact format
679
+ jwt_token = create_jwt(json_body)
680
+ requests.post(url, data=json_body, headers=headers)
681
+ \`\`\`
682
+ ` : '**No request body - No MD5 consistency concerns for GET requests.**'}
683
+
684
+ ### Troubleshooting Authentication Errors
685
+
686
+ **ERR_INVALID_MD5** - MD5 hash mismatch:
687
+ - Cause: JWT contentMd5 doesn't match HTTP request body bytes
688
+ - Fix: Use identical JSON serialization for both MD5 calculation and HTTP request
689
+ - Common issue: \`requests.post(json=data)\` vs \`requests.post(data=json_string)\`
690
+
691
+ **ERR_INVALID_SIGNATURE** - JWT signature validation failed:
692
+ - Cause: Private key mismatch or malformed JWT
693
+ - Fix: Ensure your private key matches the public key registered with payware
694
+ - Check: JWT structure, algorithm (RS256), audience (https://payware.eu)
695
+
696
+ ### Language-Specific Best Practices
697
+
698
+ **Python (requests library):**
699
+ \`\`\`python
700
+ import json
701
+ import requests
702
+
703
+ # CRITICAL: Use same serialization for both
704
+ json_body = json.dumps(payload, separators=(',', ':'))
705
+ jwt_token = create_jwt_token(json_body)
706
+ response = requests.post(url, data=json_body, headers={'Authorization': f'Bearer {jwt_token}'})
707
+ \`\`\`
708
+
709
+ **Node.js (axios library):**
710
+ \`\`\`javascript
711
+ const jsonBody = JSON.stringify(payload);
712
+ const jwtToken = createJWTToken(jsonBody);
713
+ const response = await axios.post(url, jsonBody, {headers: {'Authorization': \`Bearer \${jwtToken}\`}});
714
+ \`\`\`
715
+
716
+ **âš ī¸ Security Notes:**
717
+ - Token is valid for sandbox environment only
718
+ - Do not share or log this token
719
+ - Generate new token for each request with different body
720
+ - Ensure your public key is registered with payware
721
+ - **Critical:** Always use matching JSON serialization for MD5 and HTTP body`
722
+ }]
723
+ };
724
+ } catch (error) {
725
+ // Provide helpful error message based on the error type
726
+ let errorMessage = `Failed to create JWT token: ${error.message}`;
727
+
728
+ if (error.message.includes('key')) {
729
+ errorMessage += `\n\n**Key Format Help:**\nYou can provide the private key in any of these formats:\n- Full PEM format with -----BEGIN/END----- headers\n- Just the base64 content (headers will be added automatically)\n- Current key info: ${keyInfo.format}`;
730
+ }
731
+
732
+ throw new Error(errorMessage);
733
+ }
734
+ }
735
+ };