@payware/mcp-server 1.2.0 → 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.
Files changed (58) hide show
  1. package/README.md +5 -0
  2. package/package.json +69 -66
  3. package/src/auth/sandbox-setup.js +186 -186
  4. package/src/isv/index.js +29 -1
  5. package/src/isv/invitations/index.js +471 -0
  6. package/src/isv/oauth2/obtain-token.js +19 -8
  7. package/src/isv/oauth2/token-helper.js +5 -2
  8. package/src/isv/poi/cancel-price.js +2 -10
  9. package/src/isv/poi/create.js +19 -0
  10. package/src/isv/poi/get-poi.js +2 -10
  11. package/src/isv/poi/get-qrcode.js +2 -10
  12. package/src/isv/poi/get-status.js +2 -10
  13. package/src/isv/poi/index.js +5 -0
  14. package/src/isv/poi/list-pois.js +2 -10
  15. package/src/isv/poi/set-price.js +217 -225
  16. package/src/isv/products/create-product.js +313 -313
  17. package/src/isv/products/update-product.js +264 -264
  18. package/src/isv/reference/index.js +249 -0
  19. package/src/isv/shops/index.js +22 -0
  20. package/src/isv/transactions/cancel-transaction.js +41 -10
  21. package/src/isv/transactions/create-transaction.js +20 -4
  22. package/src/isv/transactions/get-status.js +18 -11
  23. package/src/isv/transactions/process-transaction.js +160 -42
  24. package/src/isv/transactions/simulate-callback.js +128 -109
  25. package/src/isv/transactions/transaction-history.js +86 -90
  26. package/src/merchant/data/generate-report.js +19 -1
  27. package/src/merchant/index.js +21 -1
  28. package/src/merchant/logs/index.js +366 -0
  29. package/src/merchant/products/create-product.js +287 -287
  30. package/src/merchant/products/update-product.js +281 -281
  31. package/src/merchant/shops/index.js +18 -0
  32. package/src/merchant/transactions/cancel-transaction.js +10 -13
  33. package/src/merchant/transactions/create-transaction.js +88 -8
  34. package/src/merchant/transactions/get-status.js +40 -11
  35. package/src/merchant/transactions/process-transaction.js +2 -10
  36. package/src/merchant/transactions/simulate-callback.js +68 -9
  37. package/src/merchant/transactions/transaction-history.js +275 -257
  38. package/src/payment-institution/transactions/create-pi-transaction.js +2 -10
  39. package/src/payment-institution/transactions/finalize-transaction.js +333 -340
  40. package/src/payment-institution/transactions/get-transaction-history.js +2 -10
  41. package/src/payment-institution/transactions/get-transaction-status.js +38 -11
  42. package/src/payment-institution/transactions/process-pi-transaction.js +2 -10
  43. package/src/payment-institution/transactions/simulate-pi-callback.js +49 -9
  44. package/src/payment-institution/transactions/soundbite-transaction.js +2 -10
  45. package/src/shared/api-errors.js +188 -0
  46. package/src/shared/auth-headers.js +62 -0
  47. package/src/shared/data/data-api.js +11 -10
  48. package/src/shared/deep-links/api.js +5 -4
  49. package/src/shared/logs/logs-api.js +242 -0
  50. package/src/shared/poi/poi-create-api.js +419 -0
  51. package/src/shared/products/products-api.js +17 -16
  52. package/src/shared/shops/shops-api.js +155 -0
  53. package/src/tools/advanced-code-generator.js +2255 -2246
  54. package/src/utils/code-examples/common/auth-examples.js +1484 -1481
  55. package/src/utils/code-examples/common/helpers.js +7 -4
  56. package/src/utils/code-examples/isv/oauth2.js +458 -600
  57. package/src/utils/code-examples/isv/transactions.js +815 -812
  58. package/MCP-SETUP.md +0 -190
@@ -1,600 +1,458 @@
1
- /**
2
- * ISV OAuth2 authentication flow examples
3
- */
4
-
5
- import { ExampleGenerator, CommonTemplates } from '../common/helpers.js';
6
-
7
- /**
8
- * OAuth2 operations for ISV
9
- */
10
- export const OAuth2Operations = {
11
- obtain_token: {
12
- description: 'Obtain OAuth2 access token for merchant access',
13
- endpoint: '/oauth2/token',
14
- method: 'POST',
15
- sampleBody: {
16
- grant_type: 'client_credentials',
17
- client_id: 'YOUR_CLIENT_ID',
18
- client_secret: 'YOUR_CLIENT_SECRET',
19
- scope: 'merchant:transactions merchant:products merchant:data'
20
- }
21
- },
22
-
23
- get_token_info: {
24
- description: 'Get information about current OAuth2 token',
25
- endpoint: '/oauth2/token/info',
26
- method: 'GET',
27
- sampleBody: null
28
- },
29
-
30
- create_token_simple: {
31
- description: 'Create simple OAuth2 token with basic scope',
32
- endpoint: '/oauth2/token/simple',
33
- method: 'POST',
34
- sampleBody: {
35
- grant_type: 'client_credentials',
36
- scope: 'merchant:read'
37
- }
38
- },
39
-
40
- refresh_token: {
41
- description: 'Refresh an existing OAuth2 token',
42
- endpoint: '/oauth2/token/refresh',
43
- method: 'POST',
44
- sampleBody: {
45
- grant_type: 'refresh_token',
46
- refresh_token: 'REFRESH_TOKEN_HERE'
47
- }
48
- },
49
-
50
- revoke_token: {
51
- description: 'Revoke OAuth2 access token',
52
- endpoint: '/oauth2/token/revoke',
53
- method: 'POST',
54
- sampleBody: {
55
- token: 'TOKEN_TO_REVOKE',
56
- token_type_hint: 'access_token'
57
- }
58
- },
59
-
60
- list_active_tokens: {
61
- description: 'List all active OAuth2 tokens for ISV',
62
- endpoint: '/oauth2/tokens',
63
- method: 'GET',
64
- sampleBody: null
65
- }
66
- };
67
-
68
- /**
69
- * Python OAuth2 Generator
70
- */
71
- export class PythonOAuth2Generator extends ExampleGenerator {
72
- constructor() {
73
- super('python');
74
- }
75
-
76
- getOperationTemplate(operation, partnerType, params = {}) {
77
- const opConfig = OAuth2Operations[operation];
78
- if (!opConfig) {
79
- throw new Error(`Unknown operation: ${operation}`);
80
- }
81
-
82
- const { merchantPartnerId = 'merchant_456', scope = 'merchant:full', ...otherParams } = params;
83
- const functionName = `${operation}_example`;
84
-
85
- return `def ${functionName}(merchant_partner_id='${merchantPartnerId}', scope='${scope}', use_sandbox=True):
86
- """${opConfig.description}"""
87
-
88
- try:
89
- # Get OAuth2 configuration
90
- client_id = os.getenv('PAYWARE_OAUTH_CLIENT_ID')
91
- client_secret = os.getenv('PAYWARE_OAUTH_CLIENT_SECRET')
92
-
93
- if not client_id or not client_secret:
94
- print("Missing OAuth2 credentials in environment")
95
- return None
96
-
97
- # Get API configuration
98
- base_url = get_api_base_url(use_sandbox)
99
- endpoint = '${opConfig.endpoint}'
100
- url = f"{base_url}{endpoint}"
101
-
102
- ${this.getRequestBodySection(operation, opConfig, merchantPartnerId)}
103
-
104
- # OAuth2 requests use application/x-www-form-urlencoded
105
- headers = {
106
- 'Content-Type': 'application/x-www-form-urlencoded',
107
- 'Api-Version': '1'
108
- }
109
-
110
- ${this.getRequestSection(opConfig.method)}
111
-
112
- if response.status_code == 200:
113
- result = response.json()
114
- print(f"${opConfig.description} successful:")
115
- print(json.dumps(result, indent=2))
116
-
117
- ${this.getResultProcessingSection(operation)}
118
- return result
119
- else:
120
- error_msg = f"OAuth2 request failed with status {response.status_code}: {response.text}"
121
- print(error_msg)
122
- return None
123
-
124
- except requests.exceptions.RequestException as e:
125
- print(f"OAuth2 request error: {str(e)}")
126
- return None
127
- except Exception as e:
128
- print(f"Unexpected error: {str(e)}")
129
- return None
130
-
131
- def get_oauth2_token_for_merchant(merchant_partner_id, scope='merchant:full', use_sandbox=True):
132
- """Convenience function to get OAuth2 token for specific merchant"""
133
-
134
- client_id = os.getenv('PAYWARE_OAUTH_CLIENT_ID')
135
- client_secret = os.getenv('PAYWARE_OAUTH_CLIENT_SECRET')
136
-
137
- if not client_id or not client_secret:
138
- raise ValueError("Missing OAuth2 credentials")
139
-
140
- base_url = get_api_base_url(use_sandbox)
141
- url = f"{base_url}/oauth2/token"
142
-
143
- data = {
144
- 'grant_type': 'client_credentials',
145
- 'client_id': client_id,
146
- 'client_secret': client_secret,
147
- 'scope': f"merchant:{merchant_partner_id} {scope}"
148
- }
149
-
150
- headers = {
151
- 'Content-Type': 'application/x-www-form-urlencoded',
152
- 'Api-Version': '1'
153
- }
154
-
155
- try:
156
- response = requests.post(url, data=data, headers=headers)
157
-
158
- if response.status_code == 200:
159
- token_data = response.json()
160
- return token_data['access_token']
161
- else:
162
- print(f"OAuth2 token request failed: {response.status_code} - {response.text}")
163
- return None
164
-
165
- except Exception as e:
166
- print(f"OAuth2 token error: {str(e)}")
167
- return None
168
-
169
- def validate_oauth2_token(access_token, use_sandbox=True):
170
- """Validate OAuth2 token and get token information"""
171
-
172
- base_url = get_api_base_url(use_sandbox)
173
- url = f"{base_url}/oauth2/token/info"
174
-
175
- headers = {
176
- 'Authorization': f'Bearer {access_token}',
177
- 'Api-Version': '1'
178
- }
179
-
180
- try:
181
- response = requests.get(url, headers=headers)
182
-
183
- if response.status_code == 200:
184
- token_info = response.json()
185
- print("Token validation successful:")
186
- print(f"- Valid: {token_info.get('active', False)}")
187
- print(f"- Scope: {token_info.get('scope', 'N/A')}")
188
- print(f"- Expires in: {token_info.get('expires_in', 'N/A')} seconds")
189
- return token_info
190
- else:
191
- print(f"Token validation failed: {response.status_code}")
192
- return None
193
-
194
- except Exception as e:
195
- print(f"Token validation error: {str(e)}")
196
- return None
197
-
198
- # Example usage
199
- if __name__ == "__main__":
200
- # Get target merchant from environment or use default
201
- target_merchant = os.getenv('PAYWARE_TARGET_MERCHANT_ID', 'merchant_example')
202
-
203
- print("=== OAuth2 Flow Example ===")
204
-
205
- # Step 1: Obtain OAuth2 token
206
- print("\\n1. Obtaining OAuth2 token...")
207
- token_result = obtain_token_example(target_merchant)
208
-
209
- if token_result and 'access_token' in token_result:
210
- access_token = token_result['access_token']
211
- print(f"✓ OAuth2 token obtained: {access_token[:20]}...")
212
-
213
- # Step 2: Validate the token
214
- print("\\n2. Validating OAuth2 token...")
215
- validation_result = validate_oauth2_token(access_token)
216
-
217
- if validation_result and validation_result.get('active'):
218
- print("✓ Token is valid and active")
219
-
220
- # Step 3: Use token for API calls
221
- print("\\n3. Token ready for API calls")
222
- print("You can now use this token with ISV JWT authentication")
223
- else:
224
- print("✗ Token validation failed")
225
- else:
226
- print("✗ Failed to obtain OAuth2 token")`;
227
- }
228
-
229
- getRequestBodySection(operation, opConfig, merchantPartnerId) {
230
- if (!opConfig.sampleBody) {
231
- return '# No request body needed for this operation\n request_data = {}';
232
- }
233
-
234
- let body = { ...opConfig.sampleBody };
235
-
236
- // Customize body based on operation
237
- if (operation === 'obtain_token') {
238
- body.scope = `merchant:${merchantPartnerId} ${body.scope}`;
239
- body.client_id = 'client_id'; // Will be replaced by actual value
240
- body.client_secret = 'client_secret'; // Will be replaced by actual value
241
- }
242
-
243
- const bodyEntries = Object.entries(body)
244
- .map(([key, value]) => {
245
- if (key === 'client_id') {
246
- return " 'client_id': client_id,";
247
- } else if (key === 'client_secret') {
248
- return " 'client_secret': client_secret,";
249
- } else {
250
- return ` '${key}': '${value}',`;
251
- }
252
- })
253
- .join('\n');
254
-
255
- return `# Prepare OAuth2 request data
256
- request_data = {
257
- ${bodyEntries}
258
- }`;
259
- }
260
-
261
- getRequestSection(method) {
262
- if (method === 'GET') {
263
- return 'response = requests.get(url, headers=headers)';
264
- } else {
265
- return 'response = requests.post(url, data=request_data, headers=headers)';
266
- }
267
- }
268
-
269
- getResultProcessingSection(operation) {
270
- const processing = {
271
- obtain_token: `# Store token information
272
- access_token = result.get('access_token')
273
- token_type = result.get('token_type', 'Bearer')
274
- expires_in = result.get('expires_in')
275
- scope = result.get('scope')
276
-
277
- print(f"Access Token: {access_token[:20]}...")
278
- print(f"Token Type: {token_type}")
279
- print(f"Expires In: {expires_in} seconds")
280
- print(f"Scope: {scope}")
281
-
282
- # Store token for later use
283
- os.environ['OAUTH2_ACCESS_TOKEN'] = access_token`,
284
-
285
- get_token_info: `# Display token information
286
- active = result.get('active', False)
287
- scope = result.get('scope', 'N/A')
288
- client_id = result.get('client_id', 'N/A')
289
- expires_in = result.get('exp', 'N/A')
290
-
291
- print(f"Token Active: {active}")
292
- print(f"Client ID: {client_id}")
293
- print(f"Scope: {scope}")
294
- print(f"Expires: {expires_in}")`,
295
-
296
- list_active_tokens: `# Display active tokens
297
- tokens = result.get('tokens', [])
298
- print(f"Found {len(tokens)} active tokens:")
299
- for token in tokens:
300
- print(f"- Token ID: {token.get('id')}")
301
- print(f" Scope: {token.get('scope')}")
302
- print(f" Created: {token.get('created_at')}")
303
- print(f" Expires: {token.get('expires_at')}")`
304
- };
305
-
306
- return processing[operation] || '# Process OAuth2 result as needed';
307
- }
308
- }
309
-
310
- /**
311
- * Node.js OAuth2 Generator
312
- */
313
- export class NodeJSOAuth2Generator extends ExampleGenerator {
314
- constructor() {
315
- super('nodejs');
316
- }
317
-
318
- getOperationTemplate(operation, partnerType, params = {}) {
319
- const opConfig = OAuth2Operations[operation];
320
- if (!opConfig) {
321
- throw new Error(`Unknown operation: ${operation}`);
322
- }
323
-
324
- const { merchantPartnerId = 'merchant_456', scope = 'merchant:full', ...otherParams } = params;
325
- const functionName = `${operation}Example`;
326
-
327
- return `async function ${functionName}(merchantPartnerId = '${merchantPartnerId}', scope = '${scope}', useSandbox = true) {
328
- /**
329
- * ${opConfig.description}
330
- */
331
-
332
- try {
333
- // Get OAuth2 configuration
334
- const clientId = process.env.PAYWARE_OAUTH_CLIENT_ID;
335
- const clientSecret = process.env.PAYWARE_OAUTH_CLIENT_SECRET;
336
-
337
- if (!clientId || !clientSecret) {
338
- console.error('Missing OAuth2 credentials in environment');
339
- return null;
340
- }
341
-
342
- // Get API configuration
343
- const baseUrl = getAPIBaseURL(useSandbox);
344
- const endpoint = '${opConfig.endpoint}';
345
- const url = \`\${baseUrl}\${endpoint}\`;
346
-
347
- ${this.getRequestBodySection(operation, opConfig, merchantPartnerId)}
348
-
349
- // OAuth2 requests use application/x-www-form-urlencoded
350
- const headers = {
351
- 'Content-Type': 'application/x-www-form-urlencoded',
352
- 'Api-Version': '1'
353
- };
354
-
355
- ${this.getRequestSection(opConfig.method)}
356
-
357
- if (response.status === 200) {
358
- const result = response.data;
359
- console.log('${opConfig.description} successful:');
360
- console.log(JSON.stringify(result, null, 2));
361
-
362
- ${this.getResultProcessingSection(operation)}
363
-
364
- return result;
365
- } else {
366
- console.error(\`OAuth2 request failed with status \${response.status}: \${response.statusText}\`);
367
- return null;
368
- }
369
-
370
- } catch (error) {
371
- if (error.response) {
372
- console.error(\`OAuth2 API Error: \${error.response.status} - \${error.response.data}\`);
373
- } else {
374
- console.error('OAuth2 Error:', error.message);
375
- }
376
- return null;
377
- }
378
- }
379
-
380
- async function getOAuth2TokenForMerchant(merchantPartnerId, scope = 'merchant:full', useSandbox = true) {
381
- /**
382
- * Convenience function to get OAuth2 token for specific merchant
383
- */
384
-
385
- const clientId = process.env.PAYWARE_OAUTH_CLIENT_ID;
386
- const clientSecret = process.env.PAYWARE_OAUTH_CLIENT_SECRET;
387
-
388
- if (!clientId || !clientSecret) {
389
- throw new Error('Missing OAuth2 credentials');
390
- }
391
-
392
- const baseUrl = getAPIBaseURL(useSandbox);
393
- const url = \`\${baseUrl}/oauth2/token\`;
394
-
395
- const data = new URLSearchParams({
396
- grant_type: 'client_credentials',
397
- client_id: clientId,
398
- client_secret: clientSecret,
399
- scope: \`merchant:\${merchantPartnerId} \${scope}\`
400
- });
401
-
402
- const headers = {
403
- 'Content-Type': 'application/x-www-form-urlencoded',
404
- 'Api-Version': '1'
405
- };
406
-
407
- try {
408
- const response = await axios.post(url, data, { headers });
409
-
410
- if (response.status === 200) {
411
- return response.data.access_token;
412
- } else {
413
- console.error(\`OAuth2 token request failed: \${response.status} - \${response.statusText}\`);
414
- return null;
415
- }
416
-
417
- } catch (error) {
418
- console.error('OAuth2 token error:', error.message);
419
- return null;
420
- }
421
- }
422
-
423
- async function validateOAuth2Token(accessToken, useSandbox = true) {
424
- /**
425
- * Validate OAuth2 token and get token information
426
- */
427
-
428
- const baseUrl = getAPIBaseURL(useSandbox);
429
- const url = \`\${baseUrl}/oauth2/token/info\`;
430
-
431
- const headers = {
432
- 'Authorization': \`Bearer \${accessToken}\`,
433
- 'Api-Version': '1'
434
- };
435
-
436
- try {
437
- const response = await axios.get(url, { headers });
438
-
439
- if (response.status === 200) {
440
- const tokenInfo = response.data;
441
- console.log('Token validation successful:');
442
- console.log(\`- Valid: \${tokenInfo.active || false}\`);
443
- console.log(\`- Scope: \${tokenInfo.scope || 'N/A'}\`);
444
- console.log(\`- Expires in: \${tokenInfo.expires_in || 'N/A'} seconds\`);
445
- return tokenInfo;
446
- } else {
447
- console.error(\`Token validation failed: \${response.status}\`);
448
- return null;
449
- }
450
-
451
- } catch (error) {
452
- console.error('Token validation error:', error.message);
453
- return null;
454
- }
455
- }
456
-
457
- // Example usage
458
- async function main() {
459
- // Get target merchant from environment or use default
460
- const targetMerchant = process.env.PAYWARE_TARGET_MERCHANT_ID || 'merchant_example';
461
-
462
- console.log('=== OAuth2 Flow Example ===');
463
-
464
- try {
465
- // Step 1: Obtain OAuth2 token
466
- console.log('\\n1. Obtaining OAuth2 token...');
467
- const tokenResult = await obtainTokenExample(targetMerchant);
468
-
469
- if (tokenResult && tokenResult.access_token) {
470
- const accessToken = tokenResult.access_token;
471
- console.log(\`✓ OAuth2 token obtained: \${accessToken.substring(0, 20)}...\`);
472
-
473
- // Step 2: Validate the token
474
- console.log('\\n2. Validating OAuth2 token...');
475
- const validationResult = await validateOAuth2Token(accessToken);
476
-
477
- if (validationResult && validationResult.active) {
478
- console.log('✓ Token is valid and active');
479
-
480
- // Step 3: Use token for API calls
481
- console.log('\\n3. Token ready for API calls');
482
- console.log('You can now use this token with ISV JWT authentication');
483
- } else {
484
- console.log('✗ Token validation failed');
485
- }
486
- } else {
487
- console.log('✗ Failed to obtain OAuth2 token');
488
- }
489
-
490
- } catch (error) {
491
- console.error('OAuth2 flow error:', error.message);
492
- }
493
- }
494
-
495
- // Run example if this file is executed directly
496
- if (require.main === module) {
497
- main().catch(console.error);
498
- }`;
499
- }
500
-
501
- getRequestBodySection(operation, opConfig, merchantPartnerId) {
502
- if (!opConfig.sampleBody) {
503
- return '// No request body needed for this operation\n const requestData = new URLSearchParams();';
504
- }
505
-
506
- let body = { ...opConfig.sampleBody };
507
-
508
- // Customize body based on operation
509
- if (operation === 'obtain_token') {
510
- body.scope = `merchant:${merchantPartnerId} ${body.scope}`;
511
- }
512
-
513
- const bodyEntries = Object.entries(body)
514
- .map(([key, value]) => {
515
- if (key === 'client_id') {
516
- return " requestData.append('client_id', clientId);";
517
- } else if (key === 'client_secret') {
518
- return " requestData.append('client_secret', clientSecret);";
519
- } else {
520
- return ` requestData.append('${key}', '${value}');`;
521
- }
522
- })
523
- .join('\n');
524
-
525
- return `// Prepare OAuth2 request data
526
- const requestData = new URLSearchParams();
527
- ${bodyEntries}`;
528
- }
529
-
530
- getRequestSection(method) {
531
- if (method === 'GET') {
532
- return 'const response = await axios.get(url, { headers });';
533
- } else {
534
- return 'const response = await axios.post(url, requestData, { headers });';
535
- }
536
- }
537
-
538
- getResultProcessingSection(operation) {
539
- const processing = {
540
- obtain_token: `// Store token information
541
- const accessToken = result.access_token;
542
- const tokenType = result.token_type || 'Bearer';
543
- const expiresIn = result.expires_in;
544
- const scope = result.scope;
545
-
546
- console.log(\`Access Token: \${accessToken.substring(0, 20)}...\`);
547
- console.log(\`Token Type: \${tokenType}\`);
548
- console.log(\`Expires In: \${expiresIn} seconds\`);
549
- console.log(\`Scope: \${scope}\`);
550
-
551
- // Store token for later use
552
- process.env.OAUTH2_ACCESS_TOKEN = accessToken;`,
553
-
554
- get_token_info: `// Display token information
555
- const active = result.active || false;
556
- const scope = result.scope || 'N/A';
557
- const clientId = result.client_id || 'N/A';
558
- const expiresIn = result.exp || 'N/A';
559
-
560
- console.log(\`Token Active: \${active}\`);
561
- console.log(\`Client ID: \${clientId}\`);
562
- console.log(\`Scope: \${scope}\`);
563
- console.log(\`Expires: \${expiresIn}\`);`,
564
-
565
- list_active_tokens: `// Display active tokens
566
- const tokens = result.tokens || [];
567
- console.log(\`Found \${tokens.length} active tokens:\`);
568
- tokens.forEach(token => {
569
- console.log(\`- Token ID: \${token.id}\`);
570
- console.log(\` Scope: \${token.scope}\`);
571
- console.log(\` Created: \${token.created_at}\`);
572
- console.log(\` Expires: \${token.expires_at}\`);
573
- });`
574
- };
575
-
576
- return processing[operation] || '// Process OAuth2 result as needed';
577
- }
578
- }
579
-
580
- /**
581
- * Export all generators
582
- */
583
- export const OAuth2Generators = {
584
- python: PythonOAuth2Generator,
585
- nodejs: NodeJSOAuth2Generator,
586
- javascript: NodeJSOAuth2Generator
587
- };
588
-
589
- /**
590
- * Generate OAuth2 example
591
- */
592
- export function generateOAuth2Example(operation, language = 'python', options = {}) {
593
- const GeneratorClass = OAuth2Generators[language];
594
- if (!GeneratorClass) {
595
- throw new Error(`Unsupported language: ${language}`);
596
- }
597
-
598
- const generator = new GeneratorClass();
599
- return generator.generateExample(operation, 'isv', options);
600
- }
1
+ /**
2
+ * ISV OAuth2 authentication flow examples
3
+ *
4
+ * Everything here targets the OAuth2 surface payware actually serves (Oauth2Controller):
5
+ *
6
+ * POST /oauth2/tokens request a token for a merchant
7
+ * GET /oauth2/tokens list this ISV's tokens
8
+ * GET /oauth2/tokens/{token} status of one token
9
+ * POST /oauth2/tokens/{token}/rotate rotate the credential, keeping the authorization
10
+ *
11
+ * These generators used to emit /oauth2/token, /oauth2/token/info, /oauth2/token/simple,
12
+ * /oauth2/token/refresh and /oauth2/token/revoke - none of which exist - with form-encoded,
13
+ * snake_case bodies and an OAuth2 `scope` parameter payware has never accepted. The live mcp tools
14
+ * always called the right endpoints, which is exactly why nobody noticed: the defect only reached
15
+ * integrators who shipped the generated code. They got 404s on the URLs (loud) and, worse, designed
16
+ * token handling around refresh and revoke semantics that do not exist (silent).
17
+ *
18
+ * The real contract: JSON body, camelCase fields, no scope. `clientId` is the merchant's partnerId
19
+ * and `clientSecret` is that merchant's secret, base64-encoded. A new token is PENDING until the
20
+ * merchant grants it; credentials expire (expiresIn seconds, 180 days by default) and are renewed by
21
+ * requesting again with the same client credentials - the merchant does not re-consent - or by
22
+ * rotating. There is no revoke call: authorization ends on the merchant's side.
23
+ */
24
+
25
+ import { ExampleGenerator } from '../common/helpers.js';
26
+
27
+ /**
28
+ * OAuth2 operations for ISV
29
+ */
30
+ export const OAuth2Operations = {
31
+ obtain_token: {
32
+ description: 'Request an OAuth2 token for a merchant (PENDING until the merchant grants it)',
33
+ endpoint: '/oauth2/tokens',
34
+ method: 'POST',
35
+ sampleBody: {
36
+ grantType: 'client_credentials',
37
+ clientId: 'MERCHANT_PARTNER_ID',
38
+ clientSecret: 'BASE64_ENCODED_MERCHANT_SECRET'
39
+ }
40
+ },
41
+
42
+ get_token_info: {
43
+ description: 'Get the status of one token (PENDING / GRANTED / REVOKED)',
44
+ endpoint: '/oauth2/tokens/{token}',
45
+ method: 'GET',
46
+ sampleBody: null
47
+ },
48
+
49
+ rotate_token: {
50
+ description: 'Rotate a token credential, keeping the merchant authorization it was granted under',
51
+ endpoint: '/oauth2/tokens/{token}/rotate',
52
+ method: 'POST',
53
+ sampleBody: null
54
+ },
55
+
56
+ list_active_tokens: {
57
+ description: 'List all OAuth2 tokens for this ISV',
58
+ endpoint: '/oauth2/tokens',
59
+ method: 'GET',
60
+ sampleBody: null
61
+ }
62
+ };
63
+
64
+ /**
65
+ * Python OAuth2 Generator
66
+ */
67
+ export class PythonOAuth2Generator extends ExampleGenerator {
68
+ constructor() {
69
+ super('python');
70
+ }
71
+
72
+ getOperationTemplate(operation, partnerType, params = {}) {
73
+ const opConfig = OAuth2Operations[operation];
74
+ if (!opConfig) {
75
+ throw new Error(`Unknown operation: ${operation}`);
76
+ }
77
+
78
+ const { merchantPartnerId = 'MERCHANT_PARTNER_ID' } = params;
79
+ const functionName = `${operation}_example`;
80
+
81
+ return `def ${functionName}(merchant_partner_id='${merchantPartnerId}', token='EXISTING_TOKEN', use_sandbox=True):
82
+ """${opConfig.description}
83
+
84
+ OAuth2 endpoints live outside /api, so the base URL has no /api suffix, and they are
85
+ authenticated with the ISV's signed JWT like every other payware call.
86
+ """
87
+
88
+ try:
89
+ # The merchant's secret, base64-encoded - not the ISV's own credentials
90
+ merchant_secret = os.getenv('PAYWARE_MERCHANT_SECRET')
91
+
92
+ # OAuth2 endpoints are served from the host root, not from /api
93
+ base_url = get_api_base_url(use_sandbox).replace('/api', '')
94
+ endpoint = '${opConfig.endpoint}'.replace('{token}', token)
95
+ url = f"{base_url}{endpoint}"
96
+
97
+ ${this.getRequestBodySection(operation, opConfig)}
98
+
99
+ # JSON, and no Api-Version header on OAuth2 calls
100
+ headers = {
101
+ 'Authorization': f'Bearer {create_isv_jwt(request_data, audience="https://payware.eu")}',
102
+ 'Content-Type': 'application/json'
103
+ }
104
+
105
+ ${this.getRequestSection(opConfig.method)}
106
+
107
+ if response.status_code == 200:
108
+ result = response.json()
109
+ print(f"${opConfig.description} - OK")
110
+ print(json.dumps(result, indent=2))
111
+
112
+ ${this.getResultProcessingSection(operation)}
113
+ return result
114
+ else:
115
+ print(f"OAuth2 request failed with status {response.status_code}: {response.text}")
116
+ return None
117
+
118
+ except requests.exceptions.RequestException as e:
119
+ print(f"OAuth2 request error: {str(e)}")
120
+ return None
121
+
122
+
123
+ def get_oauth2_token_for_merchant(merchant_partner_id, use_sandbox=True):
124
+ """Request a token for one merchant. Returns the accessToken, PENDING until granted."""
125
+
126
+ merchant_secret = os.getenv('PAYWARE_MERCHANT_SECRET')
127
+ if not merchant_secret:
128
+ raise ValueError("Missing merchant secret")
129
+
130
+ base_url = get_api_base_url(use_sandbox).replace('/api', '')
131
+ url = f"{base_url}/oauth2/tokens"
132
+
133
+ request_data = {
134
+ 'grantType': 'client_credentials',
135
+ 'clientId': merchant_partner_id,
136
+ 'clientSecret': base64.b64encode(merchant_secret.encode()).decode()
137
+ }
138
+
139
+ headers = {
140
+ 'Authorization': f'Bearer {create_isv_jwt(request_data, audience="https://payware.eu")}',
141
+ 'Content-Type': 'application/json'
142
+ }
143
+
144
+ response = requests.post(url, json=request_data, headers=headers)
145
+
146
+ if response.status_code == 200:
147
+ # camelCase: accessToken, tokenType, status, expiresIn, scope
148
+ return response.json()['accessToken']
149
+
150
+ print(f"OAuth2 token request failed: {response.status_code} - {response.text}")
151
+ return None
152
+
153
+
154
+ def get_token_status(token, use_sandbox=True):
155
+ """Status of an existing token: PENDING, GRANTED or REVOKED."""
156
+
157
+ base_url = get_api_base_url(use_sandbox).replace('/api', '')
158
+ url = f"{base_url}/oauth2/tokens/{token}"
159
+
160
+ headers = {
161
+ 'Authorization': f'Bearer {create_isv_jwt(None, audience="https://payware.eu")}'
162
+ }
163
+
164
+ response = requests.get(url, headers=headers)
165
+
166
+ if response.status_code == 200:
167
+ info = response.json()
168
+ print(f"- Status: {info.get('status')}")
169
+ print(f"- Client ID: {info.get('clientId')}")
170
+ print(f"- Scope: {info.get('scope')}")
171
+ print(f"- Expires in: {info.get('expiresIn')} seconds")
172
+ return info
173
+
174
+ print(f"Token status request failed: {response.status_code}")
175
+ return None
176
+
177
+
178
+ # Example usage
179
+ if __name__ == "__main__":
180
+ target_merchant = os.getenv('PAYWARE_TARGET_MERCHANT_ID', 'MERCHANT_PARTNER_ID')
181
+
182
+ print("=== OAuth2 Flow ===")
183
+
184
+ print("\\n1. Requesting a token for the merchant...")
185
+ access_token = get_oauth2_token_for_merchant(target_merchant)
186
+
187
+ if access_token:
188
+ print(f"OK - token: {access_token[:20]}...")
189
+
190
+ print("\\n2. Checking its status...")
191
+ info = get_token_status(access_token)
192
+
193
+ if info and info.get('status') == 'GRANTED':
194
+ print("The merchant has granted authorization - the token can be used.")
195
+ else:
196
+ print("Still PENDING: the merchant has to grant it in the payware portal.")
197
+ else:
198
+ print("Failed to obtain an OAuth2 token")`;
199
+ }
200
+
201
+ getRequestBodySection(operation, opConfig) {
202
+ if (!opConfig.sampleBody) {
203
+ return ' # No request body for this operation\n request_data = None';
204
+ }
205
+
206
+ return ` # JSON body, camelCase fields - clientSecret is base64 of the merchant's secret
207
+ request_data = {
208
+ 'grantType': 'client_credentials',
209
+ 'clientId': merchant_partner_id,
210
+ 'clientSecret': base64.b64encode(merchant_secret.encode()).decode()
211
+ }`;
212
+ }
213
+
214
+ getRequestSection(method) {
215
+ if (method === 'GET') {
216
+ return 'response = requests.get(url, headers=headers)';
217
+ }
218
+ return 'response = requests.post(url, json=request_data, headers=headers)';
219
+ }
220
+
221
+ getResultProcessingSection(operation) {
222
+ const processing = {
223
+ obtain_token: ` access_token = result.get('accessToken')
224
+ print(f"Access Token: {access_token[:20]}...")
225
+ print(f"Token Type: {result.get('tokenType')}")
226
+ print(f"Status: {result.get('status')}")
227
+ print(f"Expires In: {result.get('expiresIn')} seconds")`,
228
+
229
+ get_token_info: ` print(f"Status: {result.get('status')}")
230
+ print(f"Client ID: {result.get('clientId')}")
231
+ print(f"Scope: {result.get('scope')}")
232
+ print(f"Expires In: {result.get('expiresIn')} seconds")`,
233
+
234
+ rotate_token: ` print(f"New Access Token: {result.get('accessToken')}")
235
+ print(f"Status: {result.get('status')}")
236
+ print("Store the new credential; the previous one stops working.")`,
237
+
238
+ list_active_tokens: ` for token_info in result:
239
+ print(f"- {token_info.get('accessToken')} ({token_info.get('status')})"
240
+ f" client={token_info.get('clientId')}")`
241
+ };
242
+
243
+ return processing[operation] || ' # Process the OAuth2 result as needed';
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Node.js OAuth2 Generator
249
+ */
250
+ export class NodeJSOAuth2Generator extends ExampleGenerator {
251
+ constructor() {
252
+ super('nodejs');
253
+ }
254
+
255
+ getOperationTemplate(operation, partnerType, params = {}) {
256
+ const opConfig = OAuth2Operations[operation];
257
+ if (!opConfig) {
258
+ throw new Error(`Unknown operation: ${operation}`);
259
+ }
260
+
261
+ const { merchantPartnerId = 'MERCHANT_PARTNER_ID' } = params;
262
+ const functionName = `${operation}Example`;
263
+
264
+ return `async function ${functionName}(merchantPartnerId = '${merchantPartnerId}', token = 'EXISTING_TOKEN', useSandbox = true) {
265
+ /**
266
+ * ${opConfig.description}
267
+ *
268
+ * OAuth2 endpoints live outside /api, so the base URL has no /api suffix, and they are
269
+ * authenticated with the ISV's signed JWT like every other payware call.
270
+ */
271
+
272
+ try {
273
+ // The merchant's secret, base64-encoded - not the ISV's own credentials
274
+ const merchantSecret = process.env.PAYWARE_MERCHANT_SECRET;
275
+
276
+ // OAuth2 endpoints are served from the host root, not from /api
277
+ const baseUrl = getAPIBaseURL(useSandbox).replace('/api', '');
278
+ const endpoint = '${opConfig.endpoint}'.replace('{token}', token);
279
+ const url = \`\${baseUrl}\${endpoint}\`;
280
+
281
+ ${this.getRequestBodySection(operation, opConfig)}
282
+
283
+ // JSON, and no Api-Version header on OAuth2 calls
284
+ const headers = {
285
+ 'Authorization': \`Bearer \${createISVJWT(requestData, 'https://payware.eu')}\`,
286
+ 'Content-Type': 'application/json'
287
+ };
288
+
289
+ ${this.getRequestSection(opConfig.method)}
290
+
291
+ const result = response.data;
292
+ console.log('${opConfig.description} - OK');
293
+ console.log(JSON.stringify(result, null, 2));
294
+
295
+ ${this.getResultProcessingSection(operation)}
296
+
297
+ return result;
298
+
299
+ } catch (error) {
300
+ if (error.response) {
301
+ console.error(\`OAuth2 API Error: \${error.response.status} - \${JSON.stringify(error.response.data)}\`);
302
+ } else {
303
+ console.error('OAuth2 Error:', error.message);
304
+ }
305
+ return null;
306
+ }
307
+ }
308
+
309
+ async function getOAuth2TokenForMerchant(merchantPartnerId, useSandbox = true) {
310
+ /**
311
+ * Request a token for one merchant. Returns the accessToken, PENDING until the merchant grants it.
312
+ */
313
+
314
+ const merchantSecret = process.env.PAYWARE_MERCHANT_SECRET;
315
+ if (!merchantSecret) {
316
+ throw new Error('Missing merchant secret');
317
+ }
318
+
319
+ const baseUrl = getAPIBaseURL(useSandbox).replace('/api', '');
320
+ const url = \`\${baseUrl}/oauth2/tokens\`;
321
+
322
+ const requestData = {
323
+ grantType: 'client_credentials',
324
+ clientId: merchantPartnerId,
325
+ clientSecret: Buffer.from(merchantSecret).toString('base64')
326
+ };
327
+
328
+ const headers = {
329
+ 'Authorization': \`Bearer \${createISVJWT(requestData, 'https://payware.eu')}\`,
330
+ 'Content-Type': 'application/json'
331
+ };
332
+
333
+ const response = await axios.post(url, requestData, { headers });
334
+ // camelCase: accessToken, tokenType, status, expiresIn, scope
335
+ return response.data.accessToken;
336
+ }
337
+
338
+ async function getTokenStatus(token, useSandbox = true) {
339
+ /**
340
+ * Status of an existing token: PENDING, GRANTED or REVOKED.
341
+ */
342
+
343
+ const baseUrl = getAPIBaseURL(useSandbox).replace('/api', '');
344
+ const url = \`\${baseUrl}/oauth2/tokens/\${token}\`;
345
+
346
+ const headers = {
347
+ 'Authorization': \`Bearer \${createISVJWT(null, 'https://payware.eu')}\`
348
+ };
349
+
350
+ const response = await axios.get(url, { headers });
351
+ const info = response.data;
352
+
353
+ console.log(\`- Status: \${info.status}\`);
354
+ console.log(\`- Client ID: \${info.clientId}\`);
355
+ console.log(\`- Scope: \${info.scope}\`);
356
+ console.log(\`- Expires in: \${info.expiresIn} seconds\`);
357
+
358
+ return info;
359
+ }
360
+
361
+ // Example usage
362
+ async function main() {
363
+ const targetMerchant = process.env.PAYWARE_TARGET_MERCHANT_ID || 'MERCHANT_PARTNER_ID';
364
+
365
+ console.log('=== OAuth2 Flow ===');
366
+
367
+ try {
368
+ console.log('\\n1. Requesting a token for the merchant...');
369
+ const accessToken = await getOAuth2TokenForMerchant(targetMerchant);
370
+
371
+ if (accessToken) {
372
+ console.log(\`OK - token: \${accessToken.substring(0, 20)}...\`);
373
+
374
+ console.log('\\n2. Checking its status...');
375
+ const info = await getTokenStatus(accessToken);
376
+
377
+ if (info && info.status === 'GRANTED') {
378
+ console.log('The merchant has granted authorization - the token can be used.');
379
+ } else {
380
+ console.log('Still PENDING: the merchant has to grant it in the payware portal.');
381
+ }
382
+ }
383
+ } catch (error) {
384
+ console.error('OAuth2 flow error:', error.message);
385
+ }
386
+ }
387
+
388
+ if (require.main === module) {
389
+ main().catch(console.error);
390
+ }`;
391
+ }
392
+
393
+ getRequestBodySection(operation, opConfig) {
394
+ if (!opConfig.sampleBody) {
395
+ return ' // No request body for this operation\n const requestData = null;';
396
+ }
397
+
398
+ return ` // JSON body, camelCase fields - clientSecret is base64 of the merchant's secret
399
+ const requestData = {
400
+ grantType: 'client_credentials',
401
+ clientId: merchantPartnerId,
402
+ clientSecret: Buffer.from(merchantSecret).toString('base64')
403
+ };`;
404
+ }
405
+
406
+ getRequestSection(method) {
407
+ if (method === 'GET') {
408
+ return 'const response = await axios.get(url, { headers });';
409
+ }
410
+ return 'const response = await axios.post(url, requestData, { headers });';
411
+ }
412
+
413
+ getResultProcessingSection(operation) {
414
+ const processing = {
415
+ obtain_token: ` console.log(\`Access Token: \${result.accessToken.substring(0, 20)}...\`);
416
+ console.log(\`Token Type: \${result.tokenType}\`);
417
+ console.log(\`Status: \${result.status}\`);
418
+ console.log(\`Expires In: \${result.expiresIn} seconds\`);`,
419
+
420
+ get_token_info: ` console.log(\`Status: \${result.status}\`);
421
+ console.log(\`Client ID: \${result.clientId}\`);
422
+ console.log(\`Scope: \${result.scope}\`);
423
+ console.log(\`Expires In: \${result.expiresIn} seconds\`);`,
424
+
425
+ rotate_token: ` console.log(\`New Access Token: \${result.accessToken}\`);
426
+ console.log(\`Status: \${result.status}\`);
427
+ console.log('Store the new credential; the previous one stops working.');`,
428
+
429
+ list_active_tokens: ` result.forEach(tokenInfo => {
430
+ console.log(\`- \${tokenInfo.accessToken} (\${tokenInfo.status}) client=\${tokenInfo.clientId}\`);
431
+ });`
432
+ };
433
+
434
+ return processing[operation] || ' // Process the OAuth2 result as needed';
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Export all generators
440
+ */
441
+ export const OAuth2Generators = {
442
+ python: PythonOAuth2Generator,
443
+ nodejs: NodeJSOAuth2Generator,
444
+ javascript: NodeJSOAuth2Generator
445
+ };
446
+
447
+ /**
448
+ * Generate OAuth2 example
449
+ */
450
+ export function generateOAuth2Example(operation, language = 'python', options = {}) {
451
+ const GeneratorClass = OAuth2Generators[language];
452
+ if (!GeneratorClass) {
453
+ throw new Error(`Unsupported language: ${language}`);
454
+ }
455
+
456
+ const generator = new GeneratorClass();
457
+ return generator.generateExample(operation, 'isv', options);
458
+ }