@payware/mcp-server 1.1.1 → 1.2.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/MCP-SETUP.md +0 -0
- package/README.md +17 -16
- package/check-keys.js +0 -0
- package/keys/README.md +0 -0
- package/mcp-config.json +0 -0
- package/package.json +1 -1
- package/payware-mcp-config.json +0 -0
- package/src/auth/rsa-keys.js +0 -0
- package/src/auth/sandbox-setup.js +2 -2
- 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 +0 -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 +0 -0
- package/src/isv/oauth2/token-helper.js +0 -0
- package/src/isv/poi/cancel-price.js +0 -0
- package/src/isv/poi/get-poi.js +0 -0
- package/src/isv/poi/get-qrcode.js +0 -0
- package/src/isv/poi/get-status.js +0 -0
- package/src/isv/poi/index.js +0 -0
- package/src/isv/poi/list-pois.js +0 -0
- package/src/isv/poi/set-price.js +0 -0
- package/src/isv/products/audios.js +0 -0
- package/src/isv/products/create-product.js +0 -0
- 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 +0 -0
- package/src/isv/transactions/cancel-transaction.js +2 -2
- package/src/isv/transactions/create-transaction.js +2 -2
- package/src/isv/transactions/get-status.js +0 -0
- package/src/isv/transactions/process-transaction.js +2 -2
- package/src/isv/transactions/simulate-callback.js +2 -2
- package/src/isv/transactions/transaction-history.js +0 -0
- 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 +0 -0
- 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 +0 -0
- package/src/merchant/products/audios.js +0 -0
- package/src/merchant/products/create-product.js +0 -0
- 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 +0 -0
- package/src/merchant/transactions/cancel-transaction.js +6 -6
- package/src/merchant/transactions/create-transaction.js +13 -13
- package/src/merchant/transactions/get-status.js +1 -1
- package/src/merchant/transactions/process-transaction.js +6 -6
- package/src/merchant/transactions/simulate-callback.js +0 -0
- package/src/merchant/transactions/transaction-history.js +1 -1
- 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 +4 -4
- package/src/payment-institution/transactions/finalize-transaction.js +3 -3
- package/src/payment-institution/transactions/get-transaction-history.js +1 -1
- package/src/payment-institution/transactions/get-transaction-status.js +1 -1
- package/src/payment-institution/transactions/process-pi-transaction.js +3 -3
- package/src/payment-institution/transactions/simulate-pi-callback.js +0 -0
- package/src/payment-institution/transactions/soundbite-transaction.js +3 -3
- package/src/proxy-server.js +0 -0
- package/src/shared/data/data-api.js +0 -0
- package/src/shared/deep-links/api.js +0 -0
- package/src/shared/index.js +0 -0
- package/src/shared/products/products-api.js +0 -0
- package/src/tools/advanced-code-generator.js +53 -53
- package/src/utils/code-examples/common/auth-examples.js +38 -38
- package/src/utils/code-examples/common/helpers.js +0 -0
- 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 +0 -0
- package/src/utils/code-examples/isv/products.js +8 -8
- package/src/utils/code-examples/isv/transactions.js +9 -9
- 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/.env.example
CHANGED
|
File without changes
|
package/MCP-SETUP.md
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -10,10 +10,11 @@ Official MCP (Model Context Protocol) server for payware payment API integration
|
|
|
10
10
|
- **ISVs (Independent Software Vendors)** - Build multi-merchant platforms with OAuth2 token management
|
|
11
11
|
- **Payment Institutions** - Process A2A transfers, handle Payment Choice Architecture with multiple payment methods
|
|
12
12
|
|
|
13
|
-
## What's New in v1.
|
|
13
|
+
## What's New in v1.2.0
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
15
|
+
- **SHA-256 Authentication** - Upgraded from MD5 to SHA-256 for JWT content hashing (`contentSha256` header)
|
|
16
|
+
- **Updated Error Codes** - New error codes: `ERR_INVALID_CONTENT_HASH`, `ERR_MISSING_CONTENT_HASH`
|
|
17
|
+
- **Backward Compatibility** - Deprecated MD5 functions retained for legacy support
|
|
17
18
|
|
|
18
19
|
## Features
|
|
19
20
|
|
|
@@ -375,7 +376,7 @@ Generate comprehensive API documentation with code examples.
|
|
|
375
376
|
- `outputFormat` (optional): markdown, html, json (default: markdown)
|
|
376
377
|
|
|
377
378
|
#### `payware_utils_format_request`
|
|
378
|
-
Format and validate API requests with deterministic JSON serialization for
|
|
379
|
+
Format and validate API requests with deterministic JSON serialization for SHA-256 consistency.
|
|
379
380
|
|
|
380
381
|
**Parameters:**
|
|
381
382
|
- `type` (required): transaction, headers, or curl
|
|
@@ -386,7 +387,7 @@ Format and validate API requests with deterministic JSON serialization for MD5 c
|
|
|
386
387
|
- `method` (optional): HTTP method (default: POST)
|
|
387
388
|
|
|
388
389
|
#### `payware_utils_format_json_deterministic`
|
|
389
|
-
Format JSON with deterministic property ordering for consistent
|
|
390
|
+
Format JSON with deterministic property ordering for consistent SHA-256 calculation.
|
|
390
391
|
|
|
391
392
|
**Parameters:**
|
|
392
393
|
- `data` (required): Object to serialize
|
|
@@ -395,7 +396,7 @@ Format JSON with deterministic property ordering for consistent MD5 calculation.
|
|
|
395
396
|
#### `payware_utils_server_info`
|
|
396
397
|
Get MCP server information and configuration.
|
|
397
398
|
|
|
398
|
-
**Note:** All transaction data is serialized using consistent property ordering to prevent
|
|
399
|
+
**Note:** All transaction data is serialized using consistent property ordering to prevent content hash mismatch errors.
|
|
399
400
|
|
|
400
401
|
## Dependencies
|
|
401
402
|
|
|
@@ -435,12 +436,12 @@ Get MCP server information and configuration.
|
|
|
435
436
|
- Validate all input parameters
|
|
436
437
|
- Implement rate limiting and monitoring
|
|
437
438
|
|
|
438
|
-
### JSON Serialization &
|
|
439
|
-
⚠️ **CRITICAL**: payware API requires deterministic JSON serialization for
|
|
439
|
+
### JSON Serialization & SHA-256 Consistency
|
|
440
|
+
⚠️ **CRITICAL**: payware API requires deterministic JSON serialization for SHA-256 calculation
|
|
440
441
|
|
|
441
442
|
- **Property Order**: JSON objects must have consistent property ordering
|
|
442
443
|
- **Minimized Format**: No whitespace or formatting in request bodies
|
|
443
|
-
- **
|
|
444
|
+
- **SHA-256 Calculation**: Must be calculated from the exact same JSON string sent to API
|
|
444
445
|
- **Implementation**: This server uses deterministic JSON serialization to ensure consistency
|
|
445
446
|
|
|
446
447
|
**Example of correct JSON format:**
|
|
@@ -449,9 +450,9 @@ Get MCP server information and configuration.
|
|
|
449
450
|
```
|
|
450
451
|
|
|
451
452
|
**Why this matters:**
|
|
452
|
-
- Different property orders produce different
|
|
453
|
-
-
|
|
454
|
-
- Server validates that JWT
|
|
453
|
+
- Different property orders produce different SHA-256 hashes
|
|
454
|
+
- Hash mismatch results in `ERR_INVALID_CONTENT_HASH` errors
|
|
455
|
+
- Server validates that JWT `contentSha256` matches request body SHA-256
|
|
455
456
|
|
|
456
457
|
## Troubleshooting
|
|
457
458
|
|
|
@@ -463,11 +464,11 @@ Get MCP server information and configuration.
|
|
|
463
464
|
- **Verification**: Use `openssl rsa -in privateKey.pem -pubout` to extract public key from private key
|
|
464
465
|
- **Status**: ✅ Resolved - Keys must be properly registered on payware partner portal
|
|
465
466
|
|
|
466
|
-
2. **
|
|
467
|
+
2. **Content Hash Mismatch Error (ERR_INVALID_CONTENT_HASH)**
|
|
467
468
|
- **Root Cause**: Inconsistent JSON property ordering
|
|
468
469
|
- **Solution**: Use deterministic JSON serialization (implemented in this server)
|
|
469
|
-
- **Symptoms**: Server logs show different expected vs actual
|
|
470
|
-
- **Prevention**: Always use `createMinimizedJSON()` from `/src/utils/json-serializer.js`
|
|
470
|
+
- **Symptoms**: Server logs show different expected vs actual SHA-256 hashes
|
|
471
|
+
- **Prevention**: Always use `createMinimizedJSON()` from `/src/core/utils/json-serializer.js`
|
|
471
472
|
- **Status**: ✅ Fixed - Deterministic JSON serialization implemented
|
|
472
473
|
|
|
473
474
|
3. **Transaction Creation Failed**
|
|
@@ -500,7 +501,7 @@ Get MCP server information and configuration.
|
|
|
500
501
|
- JWT token creation with RS256 algorithm
|
|
501
502
|
- Transaction creation (POST /api/transactions)
|
|
502
503
|
- Transaction cancellation (PATCH /api/transactions/{id})
|
|
503
|
-
- Deterministic JSON serialization for
|
|
504
|
+
- Deterministic JSON serialization for SHA-256 consistency
|
|
504
505
|
- Public/private key pair validation
|
|
505
506
|
|
|
506
507
|
**🧪 Test Results:**
|
package/check-keys.js
CHANGED
|
File without changes
|
package/keys/README.md
CHANGED
|
File without changes
|
package/mcp-config.json
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/payware-mcp-config.json
CHANGED
|
File without changes
|
package/src/auth/rsa-keys.js
CHANGED
|
File without changes
|
|
@@ -155,13 +155,13 @@ ${JSON.stringify(config, null, 2)}
|
|
|
155
155
|
|
|
156
156
|
**Required Headers (All Requests):**
|
|
157
157
|
${Object.entries(config.headers).map(([key, value]) => `- ${key}: ${value}`).join('\n')}
|
|
158
|
-
- Authorization: Bearer {JWT_TOKEN} (with
|
|
158
|
+
- Authorization: Bearer {JWT_TOKEN} (with contentSha256 for POST/PATCH)
|
|
159
159
|
|
|
160
160
|
**JWT Requirements:**
|
|
161
161
|
- Algorithm: RS256
|
|
162
162
|
- Issuer (iss): Your Partner ID
|
|
163
163
|
- Audience (aud): https://payware.eu
|
|
164
|
-
- Content-
|
|
164
|
+
- Content-SHA256: Required in JWT header for POST/PATCH requests
|
|
165
165
|
- Private Key: RSA 2048-bit minimum
|
|
166
166
|
|
|
167
167
|
**Required Fields for Transaction Creation:**
|
package/src/config/env.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -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
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/isv/poi/get-poi.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/isv/poi/index.js
CHANGED
|
File without changes
|