@payware/mcp-server 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +0 -0
- package/README.md +22 -16
- package/check-keys.js +0 -0
- package/keys/README.md +0 -0
- package/mcp-config.json +0 -0
- package/package.json +69 -66
- package/payware-mcp-config.json +0 -0
- package/src/auth/rsa-keys.js +0 -0
- package/src/auth/sandbox-setup.js +186 -186
- package/src/config/env.js +0 -0
- package/src/config/partner-types.js +0 -0
- package/src/core/auth/jwt-factory.js +4 -4
- package/src/core/auth/jwt-token.js +107 -66
- package/src/core/utils/json-serializer.js +5 -5
- package/src/core/utils/key-utils.js +0 -0
- package/src/index.js +0 -0
- package/src/isv/data/index.js +0 -0
- package/src/isv/deep-links/delete-product-link.js +0 -0
- package/src/isv/deep-links/delete-transaction-link.js +0 -0
- package/src/isv/deep-links/get-product-link.js +0 -0
- package/src/isv/deep-links/get-transaction-link.js +0 -0
- package/src/isv/deep-links/index.js +0 -0
- package/src/isv/index.js +29 -1
- package/src/isv/invitations/index.js +471 -0
- package/src/isv/oauth2/create-token-simple.js +0 -0
- package/src/isv/oauth2/get-token-info.js +0 -0
- package/src/isv/oauth2/obtain-token.js +19 -8
- package/src/isv/oauth2/token-helper.js +5 -2
- package/src/isv/poi/cancel-price.js +2 -10
- package/src/isv/poi/create.js +19 -0
- package/src/isv/poi/get-poi.js +2 -10
- package/src/isv/poi/get-qrcode.js +2 -10
- package/src/isv/poi/get-status.js +2 -10
- package/src/isv/poi/index.js +5 -0
- package/src/isv/poi/list-pois.js +2 -10
- package/src/isv/poi/set-price.js +217 -225
- package/src/isv/products/audios.js +0 -0
- package/src/isv/products/create-product.js +313 -313
- package/src/isv/products/delete-product.js +0 -0
- package/src/isv/products/get-product-image.js +0 -0
- package/src/isv/products/get-product.js +0 -0
- package/src/isv/products/index.js +0 -0
- package/src/isv/products/list-products.js +0 -0
- package/src/isv/products/schedules.js +0 -0
- package/src/isv/products/update-product.js +264 -264
- package/src/isv/reference/index.js +249 -0
- package/src/isv/shops/index.js +22 -0
- package/src/isv/transactions/cancel-transaction.js +43 -12
- package/src/isv/transactions/create-transaction.js +22 -6
- package/src/isv/transactions/get-status.js +18 -11
- package/src/isv/transactions/process-transaction.js +160 -42
- package/src/isv/transactions/simulate-callback.js +128 -109
- package/src/isv/transactions/transaction-history.js +86 -90
- package/src/isv/utils/auth.js +2 -2
- package/src/mcp-proxy.js +0 -0
- package/src/merchant/data/export-report.js +0 -0
- package/src/merchant/data/generate-report.js +19 -1
- package/src/merchant/data/get-export-list.js +0 -0
- package/src/merchant/data/get-report-requests.js +0 -0
- package/src/merchant/data/get-report-status.js +0 -0
- package/src/merchant/data/index.js +0 -0
- package/src/merchant/data/list-reports.js +0 -0
- package/src/merchant/deep-links/delete-product-link.js +0 -0
- package/src/merchant/deep-links/delete-transaction-link.js +0 -0
- package/src/merchant/deep-links/get-product-link.js +0 -0
- package/src/merchant/deep-links/get-transaction-link.js +0 -0
- package/src/merchant/deep-links/index.js +0 -0
- package/src/merchant/index.js +21 -1
- package/src/merchant/logs/index.js +366 -0
- package/src/merchant/products/audios.js +0 -0
- package/src/merchant/products/create-product.js +287 -287
- package/src/merchant/products/delete-product.js +0 -0
- package/src/merchant/products/get-product-image.js +0 -0
- package/src/merchant/products/get-product.js +0 -0
- package/src/merchant/products/index.js +0 -0
- package/src/merchant/products/list-products.js +0 -0
- package/src/merchant/products/schedules.js +0 -0
- package/src/merchant/products/update-product.js +281 -281
- package/src/merchant/shops/index.js +18 -0
- package/src/merchant/transactions/cancel-transaction.js +16 -19
- package/src/merchant/transactions/create-transaction.js +99 -19
- package/src/merchant/transactions/get-status.js +41 -12
- package/src/merchant/transactions/process-transaction.js +8 -16
- package/src/merchant/transactions/simulate-callback.js +68 -9
- package/src/merchant/transactions/transaction-history.js +275 -257
- package/src/payment-institution/data/index.js +0 -0
- package/src/payment-institution/deep-links/delete-transaction-link.js +0 -0
- package/src/payment-institution/deep-links/get-transaction-link.js +0 -0
- package/src/payment-institution/deep-links/index.js +0 -0
- package/src/payment-institution/index.js +0 -0
- package/src/payment-institution/transactions/create-pi-transaction.js +6 -14
- package/src/payment-institution/transactions/finalize-transaction.js +333 -340
- package/src/payment-institution/transactions/get-transaction-history.js +3 -11
- package/src/payment-institution/transactions/get-transaction-status.js +39 -12
- package/src/payment-institution/transactions/process-pi-transaction.js +5 -13
- package/src/payment-institution/transactions/simulate-pi-callback.js +49 -9
- package/src/payment-institution/transactions/soundbite-transaction.js +5 -13
- package/src/proxy-server.js +0 -0
- package/src/shared/api-errors.js +188 -0
- package/src/shared/auth-headers.js +62 -0
- package/src/shared/data/data-api.js +11 -10
- package/src/shared/deep-links/api.js +5 -4
- package/src/shared/index.js +0 -0
- package/src/shared/logs/logs-api.js +242 -0
- package/src/shared/poi/poi-create-api.js +419 -0
- package/src/shared/products/products-api.js +17 -16
- package/src/shared/shops/shops-api.js +155 -0
- package/src/tools/advanced-code-generator.js +2255 -2246
- package/src/utils/code-examples/common/auth-examples.js +1484 -1481
- package/src/utils/code-examples/common/helpers.js +7 -4
- package/src/utils/code-examples/common/utils-examples.js +0 -0
- package/src/utils/code-examples/frameworks/examples.js +4 -4
- package/src/utils/code-examples/index.js +0 -0
- package/src/utils/code-examples/isv/oauth2.js +458 -600
- package/src/utils/code-examples/isv/products.js +8 -8
- package/src/utils/code-examples/isv/transactions.js +815 -812
- package/src/utils/code-examples/merchant/products.js +0 -0
- package/src/utils/code-examples/merchant/transactions.js +9 -9
- package/src/utils/code-examples/payment-institution/transactions.js +0 -0
- package/src/utils/code-examples/shared/data.js +0 -0
- package/src/utils/code-examples/shared/deep-links.js +0 -0
- package/src/utils/code-examples/shared/soundbites.js +0 -0
- package/src/utils/code-examples.js +24 -24
- package/src/utils/format-request.js +3 -3
- package/src/utils/json-formatter.js +20 -20
- package/src/utils/server-info.js +0 -0
- package/MCP-SETUP.md +0 -190
|
@@ -99,7 +99,7 @@ async function createISVJWT({ isvPartnerId, merchantPartnerId, oauth2Token, priv
|
|
|
99
99
|
* Create JWT token with custom payload (for ISV use case)
|
|
100
100
|
* @param {Object} payload - Custom JWT payload
|
|
101
101
|
* @param {string} privateKey - Private key for signing
|
|
102
|
-
* @param {Object} requestBody - Request body for content
|
|
102
|
+
* @param {Object} requestBody - Request body for content SHA-256
|
|
103
103
|
* @returns {Object} JWT token data
|
|
104
104
|
*/
|
|
105
105
|
async function createJWTTokenWithCustomPayload(payload, privateKey, requestBody) {
|
|
@@ -116,11 +116,11 @@ async function createJWTTokenWithCustomPayload(payload, privateKey, requestBody)
|
|
|
116
116
|
typ: 'JWT'
|
|
117
117
|
};
|
|
118
118
|
|
|
119
|
-
// Add
|
|
119
|
+
// Add contentSha256 to header if request body exists (SHA-256 is preferred over MD5)
|
|
120
120
|
if (requestBody) {
|
|
121
121
|
const { createMinimizedJSON } = await import('../utils/json-serializer.js');
|
|
122
122
|
const bodyString = createMinimizedJSON(requestBody);
|
|
123
|
-
header.
|
|
123
|
+
header.contentSha256 = crypto.default.createHash('sha256').update(bodyString, 'utf8').digest('base64');
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
const token = jwt.default.sign(payload, normalizedKey, {
|
|
@@ -134,7 +134,7 @@ async function createJWTTokenWithCustomPayload(payload, privateKey, requestBody)
|
|
|
134
134
|
audience: payload.aud,
|
|
135
135
|
subject: payload.sub,
|
|
136
136
|
issuedAt: new Date(payload.iat * 1000).toISOString(),
|
|
137
|
-
|
|
137
|
+
contentSha256: header.contentSha256 || null,
|
|
138
138
|
hasBody: !!requestBody,
|
|
139
139
|
isISV: true
|
|
140
140
|
};
|
|
@@ -5,14 +5,36 @@ import { getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
|
|
|
5
5
|
import { createDeterministicJSON } from '../utils/json-serializer.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Generate
|
|
8
|
+
* Generate SHA-256 hash for JWT contentSha256 header (PREFERRED)
|
|
9
9
|
*
|
|
10
|
-
* PAYWARE REQUIREMENT: The
|
|
10
|
+
* PAYWARE REQUIREMENT: The SHA-256 hash must be calculated from the EXACT same
|
|
11
11
|
* compact JSON string that will be sent as the HTTP request body.
|
|
12
12
|
*
|
|
13
13
|
* CRITICAL: Any difference in whitespace, property order, or formatting
|
|
14
|
-
* between the string used for
|
|
15
|
-
* failures with
|
|
14
|
+
* between the string used for hash and the HTTP body will cause authentication
|
|
15
|
+
* failures with ERR_INVALID_CONTENT_HASH errors.
|
|
16
|
+
*
|
|
17
|
+
* @param {Object|string} body - Request body (object will be consistently serialized)
|
|
18
|
+
* @returns {string} Base64 encoded SHA-256 hash for JWT contentSha256 header
|
|
19
|
+
*/
|
|
20
|
+
export function generateContentSha256(body) {
|
|
21
|
+
if (!body) return null;
|
|
22
|
+
|
|
23
|
+
// CRITICAL: Use deterministic JSON serialization with sorted keys
|
|
24
|
+
// This ensures consistent hashes regardless of property order
|
|
25
|
+
const bodyString = typeof body === 'string' ? body : createDeterministicJSON(body);
|
|
26
|
+
|
|
27
|
+
// Generate SHA-256 hash as raw binary and encode in base64
|
|
28
|
+
const hash = crypto.createHash('sha256');
|
|
29
|
+
hash.update(bodyString, 'utf8');
|
|
30
|
+
return hash.digest('base64');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate MD5 hash for JWT contentMd5 header (DEPRECATED - use generateContentSha256)
|
|
35
|
+
*
|
|
36
|
+
* @deprecated Use generateContentSha256 instead. MD5 is still supported by the server
|
|
37
|
+
* for backwards compatibility but is deprecated.
|
|
16
38
|
*
|
|
17
39
|
* @param {Object|string} body - Request body (object will be consistently serialized)
|
|
18
40
|
* @returns {string} Base64 encoded MD5 hash for JWT contentMd5 header
|
|
@@ -20,8 +42,6 @@ import { createDeterministicJSON } from '../utils/json-serializer.js';
|
|
|
20
42
|
export function generateContentMd5(body) {
|
|
21
43
|
if (!body) return null;
|
|
22
44
|
|
|
23
|
-
// CRITICAL: Use deterministic JSON serialization with sorted keys
|
|
24
|
-
// This ensures consistent MD5 hashes regardless of property order
|
|
25
45
|
const bodyString = typeof body === 'string' ? body : createDeterministicJSON(body);
|
|
26
46
|
|
|
27
47
|
// Generate MD5 hash as raw binary and encode in base64
|
|
@@ -31,17 +51,17 @@ export function generateContentMd5(body) {
|
|
|
31
51
|
}
|
|
32
52
|
|
|
33
53
|
/**
|
|
34
|
-
* Generate the exact JSON string for both JWT
|
|
54
|
+
* Generate the exact JSON string for both JWT content hash and HTTP request body
|
|
35
55
|
*
|
|
36
56
|
* PAYWARE REQUIREMENT: The same compact JSON string must be used for:
|
|
37
|
-
* 1. JWT
|
|
57
|
+
* 1. JWT contentSha256 calculation (passed to generateContentSha256)
|
|
38
58
|
* 2. HTTP request body (sent to payware API)
|
|
39
59
|
*
|
|
40
60
|
* This function guarantees consistency by using deterministic serialization.
|
|
41
61
|
* Any mismatch between these two uses will result in authentication failure.
|
|
42
62
|
*
|
|
43
63
|
* @param {Object} payload - Request payload object
|
|
44
|
-
* @returns {string} Compact JSON string for both
|
|
64
|
+
* @returns {string} Compact JSON string for both hash calculation and HTTP body
|
|
45
65
|
*/
|
|
46
66
|
export function serializePayload(payload) {
|
|
47
67
|
if (!payload) return null;
|
|
@@ -71,11 +91,11 @@ export function createJWTToken(partnerId, privateKey, requestBody = null) {
|
|
|
71
91
|
typ: 'JWT'
|
|
72
92
|
};
|
|
73
93
|
|
|
74
|
-
// Add
|
|
94
|
+
// Add contentSha256 to header if request body exists (for POST/PUT/PATCH)
|
|
75
95
|
// CRITICAL: The requestBody here must be the EXACT same string that will
|
|
76
|
-
// be sent as the HTTP request body, otherwise
|
|
96
|
+
// be sent as the HTTP request body, otherwise hash will not match
|
|
77
97
|
if (requestBody) {
|
|
78
|
-
header.
|
|
98
|
+
header.contentSha256 = generateContentSha256(requestBody);
|
|
79
99
|
}
|
|
80
100
|
|
|
81
101
|
// JWT Payload (as per documentation)
|
|
@@ -95,7 +115,7 @@ export function createJWTToken(partnerId, privateKey, requestBody = null) {
|
|
|
95
115
|
partnerId,
|
|
96
116
|
audience: 'https://payware.eu',
|
|
97
117
|
issuedAt: new Date(now * 1000).toISOString(),
|
|
98
|
-
|
|
118
|
+
contentSha256: header.contentSha256 || null,
|
|
99
119
|
hasBody: !!requestBody
|
|
100
120
|
};
|
|
101
121
|
}
|
|
@@ -125,7 +145,7 @@ export function createRequestSignature(method, path, body, privateKey) {
|
|
|
125
145
|
*/
|
|
126
146
|
export const validateJWTTokenTool = {
|
|
127
147
|
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",
|
|
148
|
+
description: "Validate and debug JWT token structure, decode payload/headers, verify content hash (SHA-256/MD5) calculation, and check RS256 signature format for payware API compliance",
|
|
129
149
|
inputSchema: {
|
|
130
150
|
type: "object",
|
|
131
151
|
properties: {
|
|
@@ -135,7 +155,7 @@ export const validateJWTTokenTool = {
|
|
|
135
155
|
},
|
|
136
156
|
expectedPayload: {
|
|
137
157
|
type: "object",
|
|
138
|
-
description: "Expected request body (optional - for
|
|
158
|
+
description: "Expected request body (optional - for content hash validation)"
|
|
139
159
|
}
|
|
140
160
|
},
|
|
141
161
|
required: ["jwtToken"],
|
|
@@ -159,6 +179,11 @@ export const validateJWTTokenTool = {
|
|
|
159
179
|
|
|
160
180
|
const { header, payload } = decoded;
|
|
161
181
|
|
|
182
|
+
// Determine which hash type is used
|
|
183
|
+
const hasContentSha256 = !!header.contentSha256;
|
|
184
|
+
const hasContentMd5 = !!header.contentMd5;
|
|
185
|
+
const hasContentHash = hasContentSha256 || hasContentMd5;
|
|
186
|
+
|
|
162
187
|
// Validation results
|
|
163
188
|
const validation = {
|
|
164
189
|
structure: true,
|
|
@@ -167,19 +192,31 @@ export const validateJWTTokenTool = {
|
|
|
167
192
|
audience: payload.aud === 'https://payware.eu',
|
|
168
193
|
issuer: !!payload.iss,
|
|
169
194
|
issuedAt: !!payload.iat,
|
|
170
|
-
|
|
195
|
+
contentHash: hasContentHash
|
|
171
196
|
};
|
|
172
197
|
|
|
173
|
-
//
|
|
174
|
-
let
|
|
175
|
-
if (expectedPayload &&
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
198
|
+
// Content hash validation if expected payload provided
|
|
199
|
+
let hashValidation = null;
|
|
200
|
+
if (expectedPayload && hasContentHash) {
|
|
201
|
+
if (hasContentSha256) {
|
|
202
|
+
const calculatedSha256 = generateContentSha256(expectedPayload);
|
|
203
|
+
hashValidation = {
|
|
204
|
+
type: 'SHA-256 (preferred)',
|
|
205
|
+
provided: header.contentSha256,
|
|
206
|
+
calculated: calculatedSha256,
|
|
207
|
+
matches: header.contentSha256 === calculatedSha256,
|
|
208
|
+
deterministicJson: createDeterministicJSON(expectedPayload)
|
|
209
|
+
};
|
|
210
|
+
} else if (hasContentMd5) {
|
|
211
|
+
const calculatedMd5 = generateContentMd5(expectedPayload);
|
|
212
|
+
hashValidation = {
|
|
213
|
+
type: 'MD5 (deprecated)',
|
|
214
|
+
provided: header.contentMd5,
|
|
215
|
+
calculated: calculatedMd5,
|
|
216
|
+
matches: header.contentMd5 === calculatedMd5,
|
|
217
|
+
deterministicJson: createDeterministicJSON(expectedPayload)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
183
220
|
}
|
|
184
221
|
|
|
185
222
|
// Check for common issues
|
|
@@ -189,11 +226,14 @@ export const validateJWTTokenTool = {
|
|
|
189
226
|
if (!validation.audience) issues.push("Audience should be 'https://payware.eu'");
|
|
190
227
|
if (!validation.issuer) issues.push("Missing issuer (iss) claim");
|
|
191
228
|
if (!validation.issuedAt) issues.push("Missing issued at (iat) claim");
|
|
192
|
-
if (
|
|
193
|
-
issues.push("MD5 hash
|
|
229
|
+
if (hasContentMd5 && !hasContentSha256) {
|
|
230
|
+
issues.push("Using deprecated MD5 hash - consider upgrading to SHA-256 (contentSha256)");
|
|
231
|
+
}
|
|
232
|
+
if (hashValidation && !hashValidation.matches) {
|
|
233
|
+
issues.push("Content hash mismatch - ensure deterministic JSON serialization with sorted keys");
|
|
194
234
|
}
|
|
195
235
|
|
|
196
|
-
const overallValid = Object.values(validation).every(v => v) && (!
|
|
236
|
+
const overallValid = Object.values(validation).every(v => v) && (!hashValidation || hashValidation.matches);
|
|
197
237
|
|
|
198
238
|
return {
|
|
199
239
|
content: [{
|
|
@@ -222,26 +262,26 @@ ${validation.type ? '✅' : '❌'} **Type**: JWT ${validation.type ? '(correct)'
|
|
|
222
262
|
${validation.audience ? '✅' : '❌'} **Audience**: https://payware.eu ${validation.audience ? '(correct)' : `(found: ${payload.aud})`}
|
|
223
263
|
${validation.issuer ? '✅' : '❌'} **Issuer**: ${payload.iss || 'Missing'}
|
|
224
264
|
${validation.issuedAt ? '✅' : '❌'} **Issued At**: ${payload.iat ? new Date(payload.iat * 1000).toISOString() : 'Missing'}
|
|
225
|
-
${validation.
|
|
265
|
+
${validation.contentHash ? '✅' : 'ℹ️'} **Content Hash**: ${header.contentSha256 ? `SHA-256: ${header.contentSha256}` : header.contentMd5 ? `MD5 (deprecated): ${header.contentMd5}` : 'Not present (OK for GET requests)'}
|
|
226
266
|
|
|
227
|
-
${
|
|
267
|
+
${hashValidation ? `## Content Hash Validation
|
|
228
268
|
|
|
229
|
-
${
|
|
230
|
-
**Provided**: \`${
|
|
231
|
-
**Calculated**: \`${
|
|
269
|
+
${hashValidation.matches ? '✅' : '❌'} **${hashValidation.type} Match**: ${hashValidation.matches ? 'Correct' : 'Mismatch detected'}
|
|
270
|
+
**Provided**: \`${hashValidation.provided}\`
|
|
271
|
+
**Calculated**: \`${hashValidation.calculated}\`
|
|
232
272
|
|
|
233
273
|
**Deterministic JSON Used**:
|
|
234
274
|
\`\`\`json
|
|
235
|
-
${
|
|
275
|
+
${hashValidation.deterministicJson}
|
|
236
276
|
\`\`\`
|
|
237
277
|
|
|
238
|
-
${!
|
|
239
|
-
⚠️ **
|
|
278
|
+
${!hashValidation.matches ? `
|
|
279
|
+
⚠️ **Hash Mismatch Debugging**:
|
|
240
280
|
|
|
241
|
-
PAYWARE REQUIREMENT: The EXACT same compact JSON string must be used for both JWT
|
|
281
|
+
PAYWARE REQUIREMENT: The EXACT same compact JSON string must be used for both JWT content hash and HTTP body.
|
|
242
282
|
|
|
243
283
|
**Common Causes**:
|
|
244
|
-
1. Different JSON strings used for JWT
|
|
284
|
+
1. Different JSON strings used for JWT hash vs HTTP body
|
|
245
285
|
2. Extra whitespace or formatting differences
|
|
246
286
|
3. Property order variations between serializations
|
|
247
287
|
|
|
@@ -268,7 +308,7 @@ ${issues.includes("Type should be 'JWT'") ? '- Set JWT type to "JWT"\n' : ''}
|
|
|
268
308
|
${issues.includes("Audience should be 'https://payware.eu'") ? '- Set audience to "https://payware.eu" (not just "payware")\n' : ''}
|
|
269
309
|
${issues.includes("Missing issuer (iss) claim") ? '- Add your partner ID as the issuer claim\n' : ''}
|
|
270
310
|
${issues.includes("Missing issued at (iat) claim") ? '- Add current Unix timestamp as issued at claim\n' : ''}
|
|
271
|
-
${issues.some(i => i.includes('
|
|
311
|
+
${issues.some(i => i.includes('hash mismatch')) ? '- Use deterministic JSON serialization with sorted keys\n- Ensure same JSON string for hash calculation and HTTP body\n' : ''}
|
|
272
312
|
` : '## ✅ No Issues Found\n\nYour JWT token is properly formatted for payware API usage.'}
|
|
273
313
|
|
|
274
314
|
## Technical Details
|
|
@@ -324,7 +364,7 @@ ${issues.some(i => i.includes('MD5 hash mismatch')) ? '- Use deterministic JSON
|
|
|
324
364
|
*/
|
|
325
365
|
export const testJWTTokenTool = {
|
|
326
366
|
name: "payware_authentication_test_jwt",
|
|
327
|
-
description: "Test JWT token creation process with detailed step-by-step breakdown, showing deterministic JSON serialization,
|
|
367
|
+
description: "Test JWT token creation process with detailed step-by-step breakdown, showing deterministic JSON serialization, SHA-256 hash calculation, and final JWT structure",
|
|
328
368
|
inputSchema: {
|
|
329
369
|
type: "object",
|
|
330
370
|
properties: {
|
|
@@ -334,7 +374,7 @@ export const testJWTTokenTool = {
|
|
|
334
374
|
},
|
|
335
375
|
requestPayload: {
|
|
336
376
|
type: "object",
|
|
337
|
-
description: "Sample request payload to demonstrate
|
|
377
|
+
description: "Sample request payload to demonstrate SHA-256 hash calculation"
|
|
338
378
|
},
|
|
339
379
|
showPrivateKey: {
|
|
340
380
|
type: "boolean",
|
|
@@ -387,27 +427,27 @@ ${deterministicJson}
|
|
|
387
427
|
|
|
388
428
|
**Key Sorting**: [${sortedKeys.join(', ')}]`);
|
|
389
429
|
|
|
390
|
-
// Step 2:
|
|
391
|
-
const
|
|
430
|
+
// Step 2: SHA-256 Calculation
|
|
431
|
+
const sha256Hash = generateContentSha256(requestPayload);
|
|
392
432
|
|
|
393
|
-
steps.push(`**Step 2:
|
|
433
|
+
steps.push(`**Step 2: SHA-256 Hash Calculation**
|
|
394
434
|
Using deterministic JSON string:
|
|
395
435
|
\`\`\`
|
|
396
436
|
${deterministicJson}
|
|
397
437
|
\`\`\`
|
|
398
438
|
|
|
399
|
-
|
|
439
|
+
SHA-256 hash (Base64): \`${sha256Hash}\`
|
|
400
440
|
|
|
401
441
|
**Process**:
|
|
402
|
-
1. Create
|
|
442
|
+
1. Create SHA-256 hash from UTF-8 bytes of deterministic JSON
|
|
403
443
|
2. Encode hash as Base64
|
|
404
|
-
3. Include in JWT header as \`
|
|
444
|
+
3. Include in JWT header as \`contentSha256\``);
|
|
405
445
|
|
|
406
446
|
// Step 3: JWT Header Construction
|
|
407
447
|
const jwtHeader = {
|
|
408
448
|
alg: 'RS256',
|
|
409
449
|
typ: 'JWT',
|
|
410
|
-
|
|
450
|
+
contentSha256: sha256Hash
|
|
411
451
|
};
|
|
412
452
|
|
|
413
453
|
steps.push(`**Step 3: JWT Header Construction**
|
|
@@ -418,7 +458,7 @@ ${JSON.stringify(jwtHeader, null, 2)}
|
|
|
418
458
|
**Requirements**:
|
|
419
459
|
- Algorithm: RS256 (RSA with SHA-256)
|
|
420
460
|
- Type: JWT
|
|
421
|
-
- Content
|
|
461
|
+
- Content SHA-256: ${sha256Hash}`);
|
|
422
462
|
|
|
423
463
|
// Step 4: JWT Payload Construction
|
|
424
464
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -498,18 +538,19 @@ ${deterministicJson}
|
|
|
498
538
|
## 🔑 **Key Insights**
|
|
499
539
|
|
|
500
540
|
1. **Deterministic JSON is Critical**: Property order MUST be sorted
|
|
501
|
-
2. **
|
|
541
|
+
2. **Hash Consistency**: Same JSON string for both SHA-256 hash and HTTP body
|
|
502
542
|
3. **Audience Requirement**: Must be "https://payware.eu" (include https://)
|
|
503
543
|
4. **RS256 Algorithm**: RSA with SHA-256 signature
|
|
504
|
-
5. **Content
|
|
544
|
+
5. **Content Hash in Header**: Not in payload, but in JWT header (use contentSha256)
|
|
505
545
|
|
|
506
546
|
## 🐛 **Common Mistakes to Avoid**
|
|
507
547
|
|
|
508
548
|
❌ Using \`JSON.stringify()\` without sorted keys
|
|
509
|
-
❌ Different JSON for
|
|
549
|
+
❌ Different JSON for hash calculation vs HTTP request
|
|
510
550
|
❌ Setting audience to "payware" instead of "https://payware.eu"
|
|
511
|
-
❌ Putting
|
|
551
|
+
❌ Putting contentSha256 in payload instead of header
|
|
512
552
|
❌ Using wrong algorithm (HS256 instead of RS256)
|
|
553
|
+
❌ Using deprecated contentMd5 instead of contentSha256
|
|
513
554
|
|
|
514
555
|
---
|
|
515
556
|
**Execution Info:**
|
|
@@ -567,7 +608,7 @@ export const createJWTTokenTool = {
|
|
|
567
608
|
},
|
|
568
609
|
requestBody: {
|
|
569
610
|
type: "object",
|
|
570
|
-
description: "Request body (for POST/PUT/PATCH requests that need
|
|
611
|
+
description: "Request body (for POST/PUT/PATCH requests that need contentSha256)"
|
|
571
612
|
}
|
|
572
613
|
},
|
|
573
614
|
additionalProperties: false
|
|
@@ -619,14 +660,14 @@ ${tokenData.token}
|
|
|
619
660
|
- Issued At (iat): ${tokenData.issuedAt}
|
|
620
661
|
- Algorithm: RS256
|
|
621
662
|
- Type: JWT
|
|
622
|
-
${tokenData.
|
|
663
|
+
${tokenData.contentSha256 ? `- Content SHA-256: ${tokenData.contentSha256}` : '- Content SHA-256: Not included (no request body)'}
|
|
623
664
|
|
|
624
665
|
**JWT Structure (Decoded):**
|
|
625
666
|
**Header:**
|
|
626
667
|
\`\`\`json
|
|
627
668
|
{
|
|
628
669
|
"alg": "RS256",
|
|
629
|
-
"typ": "JWT"${tokenData.
|
|
670
|
+
"typ": "JWT"${tokenData.contentSha256 ? `,\n "contentSha256": "${tokenData.contentSha256}"` : ''}
|
|
630
671
|
}
|
|
631
672
|
\`\`\`
|
|
632
673
|
|
|
@@ -653,7 +694,7 @@ Content-Type: application/json
|
|
|
653
694
|
- ✅ Issuer: Your Partner ID
|
|
654
695
|
- ✅ Audience: https://payware.eu
|
|
655
696
|
- ✅ Issued At: Unix timestamp
|
|
656
|
-
${tokenData.
|
|
697
|
+
${tokenData.contentSha256 ? '- ✅ Content SHA-256: Included for request body' : '- ℹ️ Content SHA-256: Not needed for GET requests'}
|
|
657
698
|
|
|
658
699
|
**📝 Private Key Formats Supported:**
|
|
659
700
|
- ✅ Full PEM format with headers/footers
|
|
@@ -663,9 +704,9 @@ ${tokenData.contentMd5 ? '- ✅ Content MD5: Included for request body' : '- ℹ
|
|
|
663
704
|
## ⚠️ **CRITICAL AUTHENTICATION REQUIREMENTS**
|
|
664
705
|
|
|
665
706
|
### JSON Body Consistency Rule
|
|
666
|
-
${tokenData.
|
|
707
|
+
${tokenData.contentSha256 ? `**Your contentSha256 (${tokenData.contentSha256}) MUST match the exact bytes sent in HTTP request.**
|
|
667
708
|
|
|
668
|
-
❌ **Common Error Causing
|
|
709
|
+
❌ **Common Error Causing ERR_INVALID_CONTENT_HASH:**
|
|
669
710
|
\`\`\`python
|
|
670
711
|
# Wrong - different serialization formats!
|
|
671
712
|
jwt_token = create_jwt(json.dumps(payload)) # One format
|
|
@@ -674,18 +715,18 @@ requests.post(url, json=payload) # Different format!
|
|
|
674
715
|
|
|
675
716
|
✅ **Correct Implementation:**
|
|
676
717
|
\`\`\`python
|
|
677
|
-
# Same serialization for both
|
|
718
|
+
# Same serialization for both hash and HTTP body
|
|
678
719
|
json_body = json.dumps(payload, separators=(',', ':')) # Compact format
|
|
679
720
|
jwt_token = create_jwt(json_body)
|
|
680
721
|
requests.post(url, data=json_body, headers=headers)
|
|
681
722
|
\`\`\`
|
|
682
|
-
` : '**No request body - No
|
|
723
|
+
` : '**No request body - No hash consistency concerns for GET requests.**'}
|
|
683
724
|
|
|
684
725
|
### Troubleshooting Authentication Errors
|
|
685
726
|
|
|
686
|
-
**
|
|
687
|
-
- Cause: JWT
|
|
688
|
-
- Fix: Use identical JSON serialization for both
|
|
727
|
+
**ERR_INVALID_CONTENT_HASH** - Content hash mismatch:
|
|
728
|
+
- Cause: JWT contentSha256 doesn't match HTTP request body bytes
|
|
729
|
+
- Fix: Use identical JSON serialization for both hash calculation and HTTP request
|
|
689
730
|
- Common issue: \`requests.post(json=data)\` vs \`requests.post(data=json_string)\`
|
|
690
731
|
|
|
691
732
|
**ERR_INVALID_SIGNATURE** - JWT signature validation failed:
|
|
@@ -718,7 +759,7 @@ const response = await axios.post(url, jsonBody, {headers: {'Authorization': \`B
|
|
|
718
759
|
- Do not share or log this token
|
|
719
760
|
- Generate new token for each request with different body
|
|
720
761
|
- Ensure your public key is registered with payware
|
|
721
|
-
- **Critical:** Always use matching JSON serialization for
|
|
762
|
+
- **Critical:** Always use matching JSON serialization for SHA-256 hash and HTTP body`
|
|
722
763
|
}]
|
|
723
764
|
};
|
|
724
765
|
} catch (error) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* JSON Consistency Utilities for payware API
|
|
3
3
|
*
|
|
4
4
|
* CORE REQUIREMENT: The exact same compact JSON string must be used for
|
|
5
|
-
* both JWT
|
|
5
|
+
* both JWT contentSha256 calculation and HTTP request body to prevent hash mismatches.
|
|
6
6
|
*
|
|
7
7
|
* SOLUTION: We use deterministic serialization (sorted keys) to guarantee
|
|
8
8
|
* consistent JSON output regardless of object property insertion order.
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
* Create consistent compact JSON string for payware API requests
|
|
16
16
|
*
|
|
17
17
|
* CORE REQUIREMENT: payware API requires that the exact same compact JSON string
|
|
18
|
-
* is used for both JWT
|
|
18
|
+
* is used for both JWT contentSha256 calculation and the HTTP request body.
|
|
19
19
|
*
|
|
20
20
|
* SOLUTION: This function uses deterministic serialization with sorted keys
|
|
21
|
-
* to guarantee the same output every time, preventing
|
|
21
|
+
* to guarantee the same output every time, preventing hash mismatch errors.
|
|
22
22
|
*
|
|
23
23
|
* @param {Object} obj - Object to serialize
|
|
24
24
|
* @returns {string} Compact JSON string with sorted keys (no whitespace)
|
|
@@ -72,7 +72,7 @@ function sortObjectKeys(obj) {
|
|
|
72
72
|
*
|
|
73
73
|
* payware REQUIRES:
|
|
74
74
|
* 1. JSON must be compact (no extra whitespace)
|
|
75
|
-
* 2. The EXACT same string must be used for JWT
|
|
75
|
+
* 2. The EXACT same string must be used for JWT contentSha256 and HTTP body
|
|
76
76
|
*
|
|
77
77
|
* This function ensures both requirements by using deterministic serialization.
|
|
78
78
|
*
|
|
@@ -86,7 +86,7 @@ export function createMinimizedJSON(requestBody) {
|
|
|
86
86
|
/**
|
|
87
87
|
* Validate that two objects produce the same JSON string
|
|
88
88
|
*
|
|
89
|
-
* This is crucial for payware API because the JWT
|
|
89
|
+
* This is crucial for payware API because the JWT contentSha256 and HTTP body
|
|
90
90
|
* must use the exact same string. This function helps test that requirement.
|
|
91
91
|
*
|
|
92
92
|
* @param {Object} obj1 - First object
|
|
File without changes
|
package/src/index.js
CHANGED
|
File without changes
|
package/src/isv/data/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/isv/index.js
CHANGED
|
@@ -28,6 +28,13 @@ import { isvDeepLinksTools } from './deep-links/index.js';
|
|
|
28
28
|
// POI tools
|
|
29
29
|
import { isvPOITools } from './poi/index.js';
|
|
30
30
|
|
|
31
|
+
// Shop tools
|
|
32
|
+
import { isvShopTools } from './shops/index.js';
|
|
33
|
+
|
|
34
|
+
// Merchant onboarding: invitations, and the reference data their forms need
|
|
35
|
+
import { isvInvitationTools } from './invitations/index.js';
|
|
36
|
+
import { isvReferenceTools } from './reference/index.js';
|
|
37
|
+
|
|
31
38
|
// Import shared tools
|
|
32
39
|
import { sharedTools } from '../shared/index.js';
|
|
33
40
|
|
|
@@ -61,7 +68,16 @@ export const isvTools = [
|
|
|
61
68
|
...isvDeepLinksTools,
|
|
62
69
|
|
|
63
70
|
// POI management
|
|
64
|
-
...isvPOITools
|
|
71
|
+
...isvPOITools,
|
|
72
|
+
|
|
73
|
+
// Shop lookup - needed before any call that takes a shopCode
|
|
74
|
+
...isvShopTools,
|
|
75
|
+
|
|
76
|
+
// Merchant onboarding. These come BEFORE a merchant exists, so they authenticate as the ISV
|
|
77
|
+
// itself rather than on behalf of anyone - they are how the merchantPartnerId + oauth2Token pair
|
|
78
|
+
// that every other tool here requires comes into existence in the first place.
|
|
79
|
+
...isvInvitationTools,
|
|
80
|
+
...isvReferenceTools
|
|
65
81
|
];
|
|
66
82
|
|
|
67
83
|
/**
|
|
@@ -104,6 +120,18 @@ export function getISVTools() {
|
|
|
104
120
|
else if (tool.name.startsWith('payware_poi_')) {
|
|
105
121
|
newName = tool.name; // Keep poi grouping
|
|
106
122
|
}
|
|
123
|
+
// Shop tools keep their shops grouping
|
|
124
|
+
else if (tool.name.startsWith('payware_shops_')) {
|
|
125
|
+
newName = tool.name; // Keep shops grouping
|
|
126
|
+
}
|
|
127
|
+
// Merchant onboarding (invitations) keeps its isv grouping
|
|
128
|
+
else if (tool.name.startsWith('payware_isv_')) {
|
|
129
|
+
newName = tool.name; // Keep isv grouping
|
|
130
|
+
}
|
|
131
|
+
// Reference data keeps its reference grouping
|
|
132
|
+
else if (tool.name.startsWith('payware_reference_')) {
|
|
133
|
+
newName = tool.name; // Keep reference grouping
|
|
134
|
+
}
|
|
107
135
|
|
|
108
136
|
return {
|
|
109
137
|
...tool,
|