@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,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
|
+
};
|