@payware/mcp-server 1.3.1 → 1.3.3
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/package.json +1 -1
- package/src/auth/sandbox-setup.js +1 -1
- package/src/core/auth/jwt-token.js +61 -51
- package/src/isv/invitations/index.js +2 -1
- package/src/isv/oauth2/get-token-info.js +7 -5
- package/src/isv/oauth2/obtain-token.js +6 -4
- package/src/isv/oauth2/token-helper.js +26 -26
- package/src/isv/poi/cancel-price.js +7 -2
- package/src/isv/poi/get-poi.js +2 -3
- package/src/isv/poi/get-status.js +194 -168
- package/src/isv/poi/list-pois.js +4 -6
- package/src/isv/poi/set-price.js +38 -53
- package/src/isv/products/create-product.js +1 -1
- package/src/isv/transactions/simulate-callback.js +19 -10
- package/src/merchant/transactions/create-transaction.js +1 -1
- package/src/merchant/transactions/simulate-callback.js +322 -313
- package/src/merchant/transactions/transaction-history.js +2 -2
- 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/simulate-pi-callback.js +385 -375
- package/src/payment-institution/transactions/soundbite-transaction.js +1 -1
- package/src/shared/poi/poi-create-api.js +1 -1
- package/src/tools/advanced-code-generator.js +14 -265
- package/src/utils/code-examples/common/auth-examples.js +1 -1
- package/src/utils/code-examples/isv/products.js +1 -1
- package/src/utils/code-examples/isv/transactions.js +1 -1
- package/src/utils/code-examples/merchant/transactions.js +1 -1
- package/src/utils/code-examples.js +2 -2
package/package.json
CHANGED
|
@@ -76,7 +76,7 @@ export function generateSandboxConfig(partnerId, privateKey = null) {
|
|
|
76
76
|
type: 'JWT',
|
|
77
77
|
algorithm: 'RS256',
|
|
78
78
|
audience: 'https://payware.eu',
|
|
79
|
-
|
|
79
|
+
requiresContentSha256: 'For POST/PUT/PATCH requests: Base64(SHA-256(body)) in the JWT header',
|
|
80
80
|
hasPrivateKey: !!privateKey
|
|
81
81
|
},
|
|
82
82
|
generatedAt: new Date().toISOString()
|
|
@@ -30,26 +30,6 @@ export function generateContentSha256(body) {
|
|
|
30
30
|
return hash.digest('base64');
|
|
31
31
|
}
|
|
32
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.
|
|
38
|
-
*
|
|
39
|
-
* @param {Object|string} body - Request body (object will be consistently serialized)
|
|
40
|
-
* @returns {string} Base64 encoded MD5 hash for JWT contentMd5 header
|
|
41
|
-
*/
|
|
42
|
-
export function generateContentMd5(body) {
|
|
43
|
-
if (!body) return null;
|
|
44
|
-
|
|
45
|
-
const bodyString = typeof body === 'string' ? body : createDeterministicJSON(body);
|
|
46
|
-
|
|
47
|
-
// Generate MD5 hash as raw binary and encode in base64
|
|
48
|
-
const hash = crypto.createHash('md5');
|
|
49
|
-
hash.update(bodyString, 'utf8');
|
|
50
|
-
return hash.digest('base64');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
33
|
/**
|
|
54
34
|
* Generate the exact JSON string for both JWT content hash and HTTP request body
|
|
55
35
|
*
|
|
@@ -143,9 +123,16 @@ export function createRequestSignature(method, path, body, privateKey) {
|
|
|
143
123
|
/**
|
|
144
124
|
* Validate and debug JWT token implementation
|
|
145
125
|
*/
|
|
126
|
+
// The rules below mirror the payware API's authentication checks. A check that is looser
|
|
127
|
+
// than the server's reports a token as valid that the API refuses; one that is stricter fails tokens the
|
|
128
|
+
// API accepts - the audience check did both kinds of harm until it learned the on-behalf form.
|
|
129
|
+
const PAYWARE_AUDIENCE = 'https://payware.eu';
|
|
130
|
+
const IAT_MAX_AGE_SECONDS = 300;
|
|
131
|
+
const IAT_MAX_FUTURE_SKEW_SECONDS = 60;
|
|
132
|
+
|
|
146
133
|
export const validateJWTTokenTool = {
|
|
147
134
|
name: "payware_authentication_validate_jwt",
|
|
148
|
-
description: "Validate and debug
|
|
135
|
+
description: "Validate and debug a payware JWT: decode header and payload, check the claims against the API's rules (algorithm, audience including the ISV on-behalf form, 5-minute freshness), and verify the contentSha256 hash against the request body when one is given",
|
|
149
136
|
inputSchema: {
|
|
150
137
|
type: "object",
|
|
151
138
|
properties: {
|
|
@@ -155,7 +142,7 @@ export const validateJWTTokenTool = {
|
|
|
155
142
|
},
|
|
156
143
|
expectedPayload: {
|
|
157
144
|
type: "object",
|
|
158
|
-
description: "
|
|
145
|
+
description: "The request body the token was made for (optional). Give it for POST, PUT and PATCH: contentSha256 is then required and checked against it. Leave it out for GET and DELETE, which carry no hash."
|
|
159
146
|
}
|
|
160
147
|
},
|
|
161
148
|
required: ["jwtToken"],
|
|
@@ -179,55 +166,76 @@ export const validateJWTTokenTool = {
|
|
|
179
166
|
|
|
180
167
|
const { header, payload } = decoded;
|
|
181
168
|
|
|
182
|
-
//
|
|
169
|
+
// Only contentSha256 counts. The server stopped accepting contentMd5 (SEC-013): a token that
|
|
170
|
+
// carries only contentMd5 is answered 401 ERR_MISSING_CONTENT_HASH, so reporting it here as a
|
|
171
|
+
// present, matching hash told an integrator a token was fine that the API would refuse.
|
|
183
172
|
const hasContentSha256 = !!header.contentSha256;
|
|
184
173
|
const hasContentMd5 = !!header.contentMd5;
|
|
185
|
-
const hasContentHash = hasContentSha256
|
|
174
|
+
const hasContentHash = hasContentSha256;
|
|
175
|
+
|
|
176
|
+
// Audience. https://payware.eu for a direct call, and always on /oauth2. An ISV acting on behalf
|
|
177
|
+
// of a merchant sets aud to that merchant's partner ID and must then carry sub = the merchant's
|
|
178
|
+
// OAuth token - the server answers ERR_MISSING_TOKEN without it. Only accepting
|
|
179
|
+
// https://payware.eu failed every on-behalf token, which is most of what an ISV sends.
|
|
180
|
+
const onBehalf = typeof payload.aud === 'string' && payload.aud !== '' && payload.aud !== PAYWARE_AUDIENCE;
|
|
181
|
+
const hasSubject = typeof payload.sub === 'string' && payload.sub.trim() !== '';
|
|
182
|
+
|
|
183
|
+
// Freshness. The server refuses a token issued more than 5 minutes ago, or more than 60 seconds
|
|
184
|
+
// in the future (ERR_STALE_TOKEN); a missing iat is reported by issuedAt.
|
|
185
|
+
const ageSeconds = payload.iat ? Math.floor(Date.now() / 1000) - payload.iat : null;
|
|
186
|
+
const stale = ageSeconds !== null && ageSeconds > IAT_MAX_AGE_SECONDS;
|
|
187
|
+
const fromTheFuture = ageSeconds !== null && ageSeconds < -IAT_MAX_FUTURE_SKEW_SECONDS;
|
|
186
188
|
|
|
187
189
|
// Validation results
|
|
188
190
|
const validation = {
|
|
189
191
|
structure: true,
|
|
190
192
|
algorithm: header.alg === 'RS256',
|
|
191
193
|
type: header.typ === 'JWT',
|
|
192
|
-
audience: payload.aud ===
|
|
194
|
+
audience: payload.aud === PAYWARE_AUDIENCE || (onBehalf && hasSubject),
|
|
193
195
|
issuer: !!payload.iss,
|
|
194
196
|
issuedAt: !!payload.iat,
|
|
195
|
-
|
|
197
|
+
freshness: !stale && !fromTheFuture,
|
|
198
|
+
// Required only on a request with a body. Without expectedPayload this tool cannot tell a GET
|
|
199
|
+
// from a POST, so an absent hash is not a failure on its own - it used to fail every GET token
|
|
200
|
+
// while the report beside it said "OK for GET requests".
|
|
201
|
+
contentHash: expectedPayload ? hasContentHash : true
|
|
196
202
|
};
|
|
197
203
|
|
|
198
204
|
// Content hash validation if expected payload provided
|
|
199
205
|
let hashValidation = null;
|
|
200
206
|
if (expectedPayload && hasContentHash) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
}
|
|
207
|
+
const calculatedSha256 = generateContentSha256(expectedPayload);
|
|
208
|
+
hashValidation = {
|
|
209
|
+
type: 'SHA-256',
|
|
210
|
+
provided: header.contentSha256,
|
|
211
|
+
calculated: calculatedSha256,
|
|
212
|
+
matches: header.contentSha256 === calculatedSha256,
|
|
213
|
+
deterministicJson: createDeterministicJSON(expectedPayload)
|
|
214
|
+
};
|
|
220
215
|
}
|
|
221
216
|
|
|
222
217
|
// Check for common issues
|
|
223
218
|
const issues = [];
|
|
224
219
|
if (!validation.algorithm) issues.push("Algorithm should be 'RS256'");
|
|
225
220
|
if (!validation.type) issues.push("Type should be 'JWT'");
|
|
226
|
-
if (!
|
|
221
|
+
if (!payload.aud) {
|
|
222
|
+
issues.push("Missing audience (aud) claim");
|
|
223
|
+
} else if (onBehalf && !hasSubject) {
|
|
224
|
+
issues.push(`aud is a merchant partner ID (${payload.aud}), which means acting on its behalf, but sub is missing - set sub to that merchant's OAuth token (ERR_MISSING_TOKEN), or use aud = ${PAYWARE_AUDIENCE} for a direct call`);
|
|
225
|
+
}
|
|
227
226
|
if (!validation.issuer) issues.push("Missing issuer (iss) claim");
|
|
228
227
|
if (!validation.issuedAt) issues.push("Missing issued at (iat) claim");
|
|
228
|
+
if (stale) {
|
|
229
|
+
issues.push(`Token is stale: issued ${ageSeconds}s ago, and the API accepts tokens up to ${IAT_MAX_AGE_SECONDS}s old (ERR_STALE_TOKEN)`);
|
|
230
|
+
}
|
|
231
|
+
if (fromTheFuture) {
|
|
232
|
+
issues.push(`iat is ${-ageSeconds}s in the future, beyond the ${IAT_MAX_FUTURE_SKEW_SECONDS}s of clock skew the API tolerates (ERR_STALE_TOKEN)`);
|
|
233
|
+
}
|
|
234
|
+
if (expectedPayload && !hasContentSha256 && !hasContentMd5) {
|
|
235
|
+
issues.push("Missing contentSha256: a request with a body must carry Base64(SHA-256(body)) in the JWT header (ERR_MISSING_CONTENT_HASH)");
|
|
236
|
+
}
|
|
229
237
|
if (hasContentMd5 && !hasContentSha256) {
|
|
230
|
-
issues.push("
|
|
238
|
+
issues.push("contentMd5 is not accepted: the API answers 401 ERR_MISSING_CONTENT_HASH. Send contentSha256 = Base64(SHA-256(body)) instead");
|
|
231
239
|
}
|
|
232
240
|
if (hashValidation && !hashValidation.matches) {
|
|
233
241
|
issues.push("Content hash mismatch - ensure deterministic JSON serialization with sorted keys");
|
|
@@ -259,10 +267,11 @@ ${JSON.stringify(payload, null, 2)}
|
|
|
259
267
|
${validation.structure ? '✅' : '❌'} **Structure**: JWT has valid 3-part structure
|
|
260
268
|
${validation.algorithm ? '✅' : '❌'} **Algorithm**: RS256 ${validation.algorithm ? '(correct)' : `(found: ${header.alg})`}
|
|
261
269
|
${validation.type ? '✅' : '❌'} **Type**: JWT ${validation.type ? '(correct)' : `(found: ${header.typ})`}
|
|
262
|
-
${validation.audience ? '✅' : '❌'} **Audience**:
|
|
270
|
+
${validation.audience ? '✅' : '❌'} **Audience**: ${payload.aud === PAYWARE_AUDIENCE ? `${PAYWARE_AUDIENCE} (direct call)` : onBehalf ? `${payload.aud} (on behalf of this merchant, sub ${hasSubject ? 'present' : 'MISSING'}; not valid on /oauth2 endpoints, which need ${PAYWARE_AUDIENCE})` : 'Missing'}
|
|
263
271
|
${validation.issuer ? '✅' : '❌'} **Issuer**: ${payload.iss || 'Missing'}
|
|
264
272
|
${validation.issuedAt ? '✅' : '❌'} **Issued At**: ${payload.iat ? new Date(payload.iat * 1000).toISOString() : 'Missing'}
|
|
265
|
-
${validation.
|
|
273
|
+
${validation.freshness ? '✅' : '❌'} **Freshness**: ${ageSeconds === null ? 'n/a' : stale ? `stale - ${ageSeconds}s old, limit ${IAT_MAX_AGE_SECONDS}s` : fromTheFuture ? `${-ageSeconds}s in the future, limit ${IAT_MAX_FUTURE_SKEW_SECONDS}s` : `${ageSeconds}s old (limit ${IAT_MAX_AGE_SECONDS}s)`}
|
|
274
|
+
${header.contentSha256 ? '✅' : validation.contentHash ? 'ℹ️' : '❌'} **Content Hash**: ${header.contentSha256 ? `SHA-256: ${header.contentSha256}` : header.contentMd5 ? `only contentMd5, which the API rejects - send contentSha256` : expectedPayload ? 'Missing - required on a request with a body' : 'Not present (fine for GET and DELETE; required on POST, PUT and PATCH with a body)'}
|
|
266
275
|
|
|
267
276
|
${hashValidation ? `## Content Hash Validation
|
|
268
277
|
|
|
@@ -305,7 +314,8 @@ ${issues.map(issue => `- ${issue}`).join('\n')}
|
|
|
305
314
|
|
|
306
315
|
${issues.includes("Algorithm should be 'RS256'") ? '- Update JWT algorithm to RS256\n' : ''}
|
|
307
316
|
${issues.includes("Type should be 'JWT'") ? '- Set JWT type to "JWT"\n' : ''}
|
|
308
|
-
${
|
|
317
|
+
${!validation.audience ? `- Set aud to "${PAYWARE_AUDIENCE}" for a direct call, or to the merchant's partner ID together with sub = its OAuth token when acting on its behalf\n` : ''}
|
|
318
|
+
${!validation.freshness ? '- Create the JWT immediately before each request: the API accepts it for 5 minutes, and check the clock of the machine that signs it\n' : ''}
|
|
309
319
|
${issues.includes("Missing issuer (iss) claim") ? '- Add your partner ID as the issuer claim\n' : ''}
|
|
310
320
|
${issues.includes("Missing issued at (iat) claim") ? '- Add current Unix timestamp as issued at claim\n' : ''}
|
|
311
321
|
${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' : ''}
|
|
@@ -550,7 +560,7 @@ ${deterministicJson}
|
|
|
550
560
|
❌ Setting audience to "payware" instead of "https://payware.eu"
|
|
551
561
|
❌ Putting contentSha256 in payload instead of header
|
|
552
562
|
❌ Using wrong algorithm (HS256 instead of RS256)
|
|
553
|
-
❌
|
|
563
|
+
❌ Sending contentMd5 - the API no longer accepts it; use contentSha256
|
|
554
564
|
|
|
555
565
|
---
|
|
556
566
|
**Execution Info:**
|
|
@@ -350,7 +350,8 @@ ${rows || 'No invitations match this filter.'}
|
|
|
350
350
|
|
|
351
351
|
**Status meanings:** ⏳ PENDING - link sent, merchant has not acted. ✅ COMPLETED - merchant accepted;
|
|
352
352
|
obtain a token with \`payware_authorization_oauth2_obtain_token\`. ⌛ EXPIRED - the link lapsed; create
|
|
353
|
-
a new invitation. 🚫 CANCELLED - withdrawn by you
|
|
353
|
+
a new invitation. 🚫 CANCELLED - withdrawn by you, or by payware because the merchant it was addressed
|
|
354
|
+
to was deleted.
|
|
354
355
|
|
|
355
356
|
**Request ID:** ${result.requestId || 'N/A'}
|
|
356
357
|
**Timestamp:** ${result.timestamp}`
|
|
@@ -76,18 +76,20 @@ Auth: Requires ISV JWT token`,
|
|
|
76
76
|
text: `📋 **OAuth2 Token Information**
|
|
77
77
|
|
|
78
78
|
🔑 **Token Details:**
|
|
79
|
-
- **Access Token**: ${result.
|
|
80
|
-
- **Token Type**: ${result.
|
|
79
|
+
- **Access Token**: ${result.accessToken}
|
|
80
|
+
- **Token Type**: ${result.tokenType}
|
|
81
81
|
- **Status**: ${statusEmoji[result.status]} ${result.status}
|
|
82
82
|
- **Description**: ${statusDescription[result.status]}
|
|
83
83
|
- **Scope**: ${result.scope}
|
|
84
|
-
- **Expires**: ${result.
|
|
84
|
+
- **Expires**: ${result.expiresIn == null ? 'No expiration date' : result.expiresIn === 0 ? 'Expired - rotate with POST /oauth2/tokens/{token}/rotate' : `${result.expiresIn} seconds (~${Math.floor(result.expiresIn / 86400)} days)`}
|
|
85
85
|
|
|
86
|
-
${result.status === 'GRANTED' ?
|
|
86
|
+
${result.status === 'GRANTED' && result.expiresIn === 0 ?
|
|
87
|
+
'⌛ **Token has expired - rotate it with POST /oauth2/tokens/{token}/rotate (no merchant consent needed)**' :
|
|
88
|
+
result.status === 'GRANTED' ?
|
|
87
89
|
'✅ **Token is ready for use in API requests**' :
|
|
88
90
|
result.status === 'PENDING' ?
|
|
89
91
|
'⏳ **Token is pending - wait for merchant approval**' :
|
|
90
|
-
'❌ **Token is revoked
|
|
92
|
+
'❌ **Token is revoked, and revocation is final.** Requesting a token again returns this same revoked token; the merchant must consent again through an AUTHORIZATION_ONLY invitation (payware_isv_create_invitation)'
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
📋 **Usage Notes:**
|
|
@@ -12,7 +12,10 @@ export const obtainTokenTool = {
|
|
|
12
12
|
ISVs must be authorized by payware registered merchants to make requests on their behalf.
|
|
13
13
|
This endpoint allows ISVs to request a token using the merchant's credentials.
|
|
14
14
|
|
|
15
|
-
IMPORTANT:
|
|
15
|
+
IMPORTANT: A token requested this way expires 180 days after the merchant grants authorization.
|
|
16
|
+
expiresIn in the response gives the seconds remaining (null = no expiration date). Rotate it with
|
|
17
|
+
POST /oauth2/tokens/{token}/rotate, before or after expiry - no merchant consent is needed.
|
|
18
|
+
Requesting again for the same merchant returns the existing token rather than a second one.
|
|
16
19
|
Store tokens securely using strong encryption.
|
|
17
20
|
|
|
18
21
|
Endpoint: POST /oauth2/tokens
|
|
@@ -99,7 +102,7 @@ Auth: Requires ISV JWT token with aud: "https://payware.eu"`,
|
|
|
99
102
|
- **Token Type**: ${result.tokenType}
|
|
100
103
|
- **Status**: ${result.status}
|
|
101
104
|
- **Scope**: ${result.scope}
|
|
102
|
-
- **Expires**: ${result.expiresIn
|
|
105
|
+
- **Expires**: ${result.expiresIn == null ? 'No expiration date' : result.expiresIn === 0 ? 'Expired - rotate with POST /oauth2/tokens/{token}/rotate' : `${result.expiresIn} seconds (~${Math.floor(result.expiresIn / 86400)} days)`}
|
|
103
106
|
|
|
104
107
|
📋 **Copy this token for next operations:**
|
|
105
108
|
\`${result.accessToken}\`
|
|
@@ -110,7 +113,7 @@ Auth: Requires ISV JWT token with aud: "https://payware.eu"`,
|
|
|
110
113
|
- Use this token in the "sub" claim of future JWT requests
|
|
111
114
|
|
|
112
115
|
📋 **Next Steps:**
|
|
113
|
-
1. Check token status: Use token \`${result.accessToken}\` with
|
|
116
|
+
1. Check token status: Use token \`${result.accessToken}\` with payware_authorization_oauth2_get_token_info
|
|
114
117
|
2. Wait for merchant approval (status will change from PENDING to GRANTED)
|
|
115
118
|
3. Use this token in JWT "sub" claim for API requests to merchant data
|
|
116
119
|
4. Set JWT "aud" claim to the merchant's partnerId (${finalClientId}) for API calls`
|
|
@@ -191,7 +194,6 @@ ${JSON.stringify(errorDetails, null, 2)}
|
|
|
191
194
|
- Verify merchant partnerId (clientId) is correct
|
|
192
195
|
- Ensure merchant secret is properly base64 encoded
|
|
193
196
|
- Check if merchant account is active
|
|
194
|
-
- Confirm no existing token for this merchant
|
|
195
197
|
- Verify ISV JWT token is properly signed for OAuth2 requests`
|
|
196
198
|
}]
|
|
197
199
|
};
|
|
@@ -54,24 +54,30 @@ Use this when you need a token but aren't sure if one exists or what it is.`,
|
|
|
54
54
|
text: `📋 **OAuth2 Token Management Help**
|
|
55
55
|
|
|
56
56
|
🔑 **About OAuth2 Tokens:**
|
|
57
|
-
-
|
|
58
|
-
- Tokens
|
|
59
|
-
-
|
|
57
|
+
- Requesting a token again for the same merchant returns the existing one, not a second one
|
|
58
|
+
- Tokens requested this way expire 180 days after the merchant grants authorization
|
|
59
|
+
- Rotate with POST /oauth2/tokens/{token}/rotate - works on expired tokens too, no merchant consent needed
|
|
60
|
+
- Token status: PENDING → GRANTED (after merchant approval); REVOKED is final
|
|
60
61
|
|
|
61
62
|
🎯 **To get your token:**
|
|
62
63
|
|
|
63
|
-
**Option 1:
|
|
64
|
-
Use: \`
|
|
65
|
-
-
|
|
64
|
+
**Option 1: Request a token**
|
|
65
|
+
Use: \`payware_authorization_oauth2_obtain_token\`
|
|
66
|
+
- Returns a new PENDING token, or the existing token if this merchant already has one
|
|
66
67
|
- On failure the server names the reason (e.g. client not found, client disabled)
|
|
67
68
|
|
|
68
69
|
**Option 2: Check a known token**
|
|
69
|
-
Use: \`
|
|
70
|
-
- Shows current status (PENDING/GRANTED/REVOKED)
|
|
70
|
+
Use: \`payware_authorization_oauth2_get_token_info\` with your saved token
|
|
71
|
+
- Shows current status (PENDING/GRANTED/REVOKED) and expiry
|
|
71
72
|
|
|
72
|
-
**Option 3:
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
**Option 3: If you lost the token**
|
|
74
|
+
Nothing needs revoking - the token can be retrieved:
|
|
75
|
+
- Request it again with Option 1, which returns the existing token, or
|
|
76
|
+
- Call GET /oauth2/tokens, which lists every token issued to your ISV with its value and status
|
|
77
|
+
|
|
78
|
+
⚠️ **Never ask the merchant to revoke a token to get a new one.** Revocation is final: requesting
|
|
79
|
+
again afterwards returns the same REVOKED token, and the only way back is a fresh consent through an
|
|
80
|
+
AUTHORIZATION_ONLY invitation (\`payware_isv_create_invitation\`). To replace a token value, rotate it.
|
|
75
81
|
|
|
76
82
|
🔐 **Security:**
|
|
77
83
|
- Always store tokens securely
|
|
@@ -94,25 +100,19 @@ Use: \`payware_oauth2_get_token_info\` with your saved token
|
|
|
94
100
|
type: 'text',
|
|
95
101
|
text: `⚠️ **Token Already Exists for This Merchant**
|
|
96
102
|
|
|
97
|
-
🔍 **
|
|
103
|
+
🔍 **A token already exists for this merchant, and it can be retrieved.**
|
|
98
104
|
|
|
99
105
|
📋 **To find your token:**
|
|
100
106
|
|
|
101
|
-
**Option 1:
|
|
102
|
-
-
|
|
103
|
-
- Environment variables file
|
|
104
|
-
- Secure configuration management
|
|
105
|
-
- Password manager
|
|
107
|
+
**Option 1: List your tokens**
|
|
108
|
+
Call GET /oauth2/tokens - it lists every token issued to your ISV with its value and status.
|
|
106
109
|
|
|
107
110
|
**Option 2: If you have the token value**
|
|
108
|
-
Use: \`
|
|
109
|
-
|
|
110
|
-
**Option 3: Create new token**
|
|
111
|
-
1. Contact the merchant (Partner ID: ${process.env.PAYWARE_OAUTH_CLIENT_ID || 'not configured'})
|
|
112
|
-
2. Ask them to revoke the existing token
|
|
113
|
-
3. Then use \`payware_oauth2_obtain_token\` to create a new one
|
|
111
|
+
Use: \`payware_authorization_oauth2_get_token_info\` with your token to check its status
|
|
114
112
|
|
|
115
|
-
|
|
113
|
+
**Option 3: Need a different value?**
|
|
114
|
+
Rotate it with POST /oauth2/tokens/{token}/rotate. Do not ask the merchant to revoke it - revocation
|
|
115
|
+
is final, and requesting again afterwards returns the same REVOKED token.
|
|
116
116
|
|
|
117
117
|
📋 **For immediate help:**
|
|
118
118
|
Run this tool with action='help' for more detailed guidance.`
|
|
@@ -138,8 +138,8 @@ ${token}
|
|
|
138
138
|
\`\`\`
|
|
139
139
|
|
|
140
140
|
🔄 **Next Steps:**
|
|
141
|
-
1. **SAVE THIS TOKEN** securely (you
|
|
142
|
-
2. Check status: \`
|
|
141
|
+
1. **SAVE THIS TOKEN** securely (if you lose it, GET /oauth2/tokens lists it again)
|
|
142
|
+
2. Check status: \`payware_authorization_oauth2_get_token_info\` with token: \`${token}\`
|
|
143
143
|
3. Wait for merchant approval (status will change to GRANTED)
|
|
144
144
|
4. Use token in API requests once GRANTED`
|
|
145
145
|
}]
|
|
@@ -61,9 +61,14 @@ export const cancelPOIPriceTool = {
|
|
|
61
61
|
|
|
62
62
|
**ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
|
|
63
63
|
**Endpoint:** DELETE /poi/{poiId}/price
|
|
64
|
-
**Use Case:** Cancel a price that was set but
|
|
64
|
+
**Use Case:** Cancel a price that was set but not yet scanned (e.g., order cancelled).
|
|
65
65
|
|
|
66
|
-
The POI
|
|
66
|
+
The POI returns to IDLE and is ready for a new price.
|
|
67
|
+
|
|
68
|
+
**Only before the scan.** The scan is the customer's bank processing the payment, so from then on a POI
|
|
69
|
+
sale cannot be cancelled by the merchant or an ISV, on any plan: this answers 409
|
|
70
|
+
ERR_POI_NO_PENDING_PAYMENT, and cancelling the transaction itself answers 409 ERR_ALREADY_PROCESSED. The
|
|
71
|
+
sale completes or expires; the POI accepts the next price meanwhile.
|
|
67
72
|
|
|
68
73
|
**Required:** POI ID, Merchant Partner ID, and OAuth2 token.`,
|
|
69
74
|
|
package/src/isv/poi/get-poi.js
CHANGED
|
@@ -115,8 +115,7 @@ export const getPOITool = {
|
|
|
115
115
|
const statusEmoji = {
|
|
116
116
|
'IDLE': '🟢',
|
|
117
117
|
'READY': '🟡',
|
|
118
|
-
'BUSY': '🔴'
|
|
119
|
-
'DISABLED': '⚫'
|
|
118
|
+
'BUSY': '🔴'
|
|
120
119
|
}[poi.status] || '⚪';
|
|
121
120
|
|
|
122
121
|
return {
|
|
@@ -137,7 +136,7 @@ export const getPOITool = {
|
|
|
137
136
|
- Linked vPOS: ${poi.linkedVposCount || 0} terminals
|
|
138
137
|
|
|
139
138
|
**Current Session:**
|
|
140
|
-
${poi.status
|
|
139
|
+
${poi.status === 'READY' && poi.pendingAmount ? `- Amount: ${poi.pendingAmount} ${poi.pendingCurrency}
|
|
141
140
|
- Expires: ${poi.sessionExpiresAt || 'N/A'}
|
|
142
141
|
- Token: ${poi.sessionToken || 'N/A'}` : '- No active session'}
|
|
143
142
|
|