@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,813 +1,816 @@
1
- /**
2
- * ISV transaction examples for all operations
3
- */
4
-
5
- import { PythonGenerator, NodeJSGenerator } from '../common/utils-examples.js';
6
- import { CommonTemplates } from '../common/helpers.js';
7
-
8
- /**
9
- * ISV transaction operations
10
- */
11
- export const ISVTransactionOperations = {
12
- create_transaction: {
13
- description: 'Create a transaction on behalf of a merchant (ISV operation)',
14
- endpoint: '/transactions',
15
- method: 'POST',
16
- requiresOAuth: true,
17
- sampleBody: {
18
- trData: {
19
- amount: '50.00',
20
- currency: 'EUR',
21
- reasonL1: 'ISV managed transaction',
22
- reasonL2: 'Created on behalf of merchant'
23
- },
24
- trOptions: {
25
- type: 'QR',
26
- timeToLive: 600
27
- }
28
- }
29
- },
30
-
31
- get_transaction_status: {
32
- description: 'Get transaction status on behalf of merchant',
33
- endpoint: '/transactions/{id}',
34
- method: 'GET',
35
- requiresOAuth: true,
36
- sampleBody: {}
37
- },
38
-
39
- cancel_transaction: {
40
- description: 'Cancel transaction on behalf of merchant',
41
- endpoint: '/transactions/{id}',
42
- method: 'PATCH',
43
- requiresOAuth: true,
44
- sampleBody: {
45
- status: 'CANCELLED',
46
- statusMessage: 'ISV initiated cancellation'
47
- }
48
- },
49
-
50
- process_transaction: {
51
- description: 'Process transaction on behalf of merchant',
52
- endpoint: '/transactions/{id}',
53
- method: 'POST',
54
- requiresOAuth: true,
55
- sampleBody: {
56
- account: 'MERCHANT_ACCOUNT_123',
57
- friendlyName: 'Merchant Name via ISV'
58
- }
59
- },
60
-
61
- get_transaction_history: {
62
- description: 'Get transaction history for merchant',
63
- endpoint: '/transactions/{id}/history',
64
- method: 'GET',
65
- requiresOAuth: true,
66
- sampleBody: {}
67
- },
68
-
69
- simulate_callback: {
70
- description: 'Simulate callback for merchant transaction',
71
- endpoint: '/transactions/{id}/callback',
72
- method: 'POST',
73
- requiresOAuth: true,
74
- sampleBody: {
75
- status: 'PROCESSED',
76
- timestamp: new Date().toISOString()
77
- }
78
- }
79
- };
80
-
81
- /**
82
- * Generate Python examples for ISV transactions
83
- */
84
- export function generateISVTransactionPython(operation, params = {}) {
85
- const operationConfig = ISVTransactionOperations[operation];
86
-
87
- if (!operationConfig) {
88
- throw new Error(`Unknown operation: ${operation}`);
89
- }
90
-
91
- const sections = [];
92
-
93
- // Header and description
94
- sections.push(`"""
95
- ${operationConfig.description}
96
-
97
- This example demonstrates ISV operations for managing transactions on behalf of merchants.
98
- Partner Type: ISV (Independent Software Vendor)
99
- Operation: ${operation}
100
- Method: ${operationConfig.method}
101
- Endpoint: ${operationConfig.endpoint}
102
- Authentication: ISV JWT + OAuth2 Token
103
- """
104
-
105
- # Required dependencies:
106
- # pip install requests pyjwt cryptography python-dotenv
107
-
108
- import requests
109
- import jwt
110
- import json
111
- import hashlib
112
- import base64
113
- import os
114
- from datetime import datetime
115
- from dotenv import load_dotenv
116
-
117
- # Load environment variables
118
- load_dotenv()`);
119
-
120
- // Environment setup
121
- sections.push(`# ISV Environment Configuration
122
- def load_isv_config():
123
- """Load ISV configuration from environment"""
124
- config = {
125
- 'isv_partner_id': os.getenv('PAYWARE_PARTNER_ID'),
126
- 'sandbox_key_path': os.getenv('PAYWARE_SANDBOX_PRIVATE_KEY_PATH'),
127
- 'production_key_path': os.getenv('PAYWARE_PRODUCTION_PRIVATE_KEY_PATH'),
128
- 'oauth_client_id': os.getenv('PAYWARE_OAUTH_CLIENT_ID'),
129
- 'oauth_client_secret': os.getenv('PAYWARE_OAUTH_CLIENT_SECRET'),
130
- 'default_merchant_id': os.getenv('PAYWARE_DEFAULT_MERCHANT_ID'),
131
- 'sandbox_url': os.getenv('PAYWARE_SANDBOX_URL', 'https://sandbox.payware.eu/api'),
132
- 'production_url': os.getenv('PAYWARE_PRODUCTION_URL', 'https://api.payware.eu/api')
133
- }
134
-
135
- # Validate required configuration
136
- required_vars = ['isv_partner_id', 'oauth_client_id', 'oauth_client_secret']
137
- for var in required_vars:
138
- if not config[var]:
139
- env_var = {
140
- 'isv_partner_id': 'PAYWARE_PARTNER_ID',
141
- 'oauth_client_id': 'PAYWARE_OAUTH_CLIENT_ID',
142
- 'oauth_client_secret': 'PAYWARE_OAUTH_CLIENT_SECRET'
143
- }[var]
144
- raise ValueError(f"{env_var} environment variable is required for ISV operations")
145
-
146
- return config`);
147
-
148
- // OAuth2 functions
149
- sections.push(`# OAuth2 Authentication for ISV
150
- def get_oauth2_token(merchant_partner_id, use_sandbox=True):
151
- """Get OAuth2 token for accessing merchant resources"""
152
- config = load_isv_config()
153
- base_url = config['sandbox_url'] if use_sandbox else config['production_url']
154
-
155
- # Prepare OAuth2 token request
156
- token_data = {
157
- 'grant_type': 'client_credentials',
158
- 'client_id': config['oauth_client_id'],
159
- 'client_secret': config['oauth_client_secret'],
160
- 'scope': f'merchant:{merchant_partner_id}'
161
- }
162
-
163
- try:
164
- response = requests.post(
165
- f"{base_url}/oauth2/token",
166
- data=token_data,
167
- headers={'Content-Type': 'application/x-www-form-urlencoded'}
168
- )
169
-
170
- if response.status_code == 200:
171
- token_response = response.json()
172
- return {
173
- 'access_token': token_response['access_token'],
174
- 'token_type': token_response['token_type'],
175
- 'expires_in': token_response.get('expires_in'),
176
- 'scope': token_response.get('scope')
177
- }
178
- else:
179
- raise Exception(f"OAuth2 token request failed: {response.status_code} - {response.text}")
180
-
181
- except Exception as e:
182
- raise Exception(f"OAuth2 authentication error: {str(e)}")
183
-
184
- def create_isv_jwt_token(merchant_partner_id, oauth2_token, request_body=None, use_sandbox=True):
185
- """Create ISV JWT token with OAuth2 token for merchant access"""
186
- config = load_isv_config()
187
-
188
- # Get private key path based on environment
189
- private_key_path = config['sandbox_key_path'] if use_sandbox else config['production_key_path']
190
- if not private_key_path:
191
- env_var = 'PAYWARE_SANDBOX_PRIVATE_KEY_PATH' if use_sandbox else 'PAYWARE_PRODUCTION_PRIVATE_KEY_PATH'
192
- raise ValueError(f"{env_var} environment variable is required")
193
-
194
- # Load ISV private key
195
- with open(private_key_path, 'r') as f:
196
- private_key = f.read()
197
-
198
- # Create JWT header
199
- header = {
200
- 'alg': 'RS256',
201
- 'typ': 'JWT'
202
- }
203
-
204
- # Add content SHA-256 for requests with body
205
- body_string = None
206
- if request_body:
207
- body_string = create_minimized_json(request_body)
208
- content_sha256 = base64.b64encode(
209
- hashlib.sha256(body_string.encode('utf-8')).digest()
210
- ).decode('utf-8')
211
- header['contentSha256'] = content_sha256
212
-
213
- # Create ISV JWT payload with OAuth2 token
214
- payload = {
215
- 'iss': config['isv_partner_id'], # ISV Partner ID
216
- 'aud': merchant_partner_id, # Target Merchant ID
217
- 'sub': oauth2_token, # OAuth2 access token
218
- 'iat': int(datetime.utcnow().timestamp())
219
- }
220
-
221
- # Create token
222
- token = jwt.encode(payload, private_key, algorithm='RS256', headers=header)
223
- return token, body_string
224
-
225
- def create_minimized_json(data):
226
- """Create deterministic minimized JSON for SHA-256 calculation"""
227
- def sort_dict(obj):
228
- if isinstance(obj, dict):
229
- return {k: sort_dict(v) for k, v in sorted(obj.items())}
230
- elif isinstance(obj, list):
231
- return [sort_dict(item) for item in obj]
232
- else:
233
- return obj
234
-
235
- sorted_data = sort_dict(data)
236
- return json.dumps(sorted_data, separators=(',', ':'))
237
-
238
- def get_api_headers(token):
239
- """Get standard API headers"""
240
- return {
241
- 'Authorization': f'Bearer {token}',
242
- 'Content-Type': 'application/json',
243
- 'Api-Version': '1'
244
- }`);
245
-
246
- // Main operation function
247
- const needsId = operationConfig.endpoint.includes('{id}');
248
- const hasBody = operationConfig.method !== 'GET' && Object.keys(operationConfig.sampleBody).length > 0;
249
-
250
- const paramList = ['merchant_partner_id', 'use_sandbox=True'];
251
- if (needsId) {
252
- paramList.splice(1, 0, 'transaction_id');
253
- }
254
-
255
- // Add custom parameters for specific operations
256
- if (operation === 'create_transaction') {
257
- paramList.splice(-1, 0, 'amount="50.00"', 'currency="EUR"', 'reason_l1="ISV transaction"', 'reason_l2=None');
258
- }
259
-
260
- const endpoint = operationConfig.endpoint.replace('{id}', '{transaction_id}');
261
-
262
- sections.push(`# Main ISV Operation
263
- def ${operation}(${paramList.join(', ')}):
264
- """${operationConfig.description}"""
265
-
266
- try:
267
- config = load_isv_config()
268
- base_url = config['sandbox_url'] if use_sandbox else config['production_url']
269
-
270
- # Step 1: Get OAuth2 token for merchant access
271
- print(f"šŸ” Getting OAuth2 token for merchant: {merchant_partner_id}")
272
- oauth2_response = get_oauth2_token(merchant_partner_id, use_sandbox)
273
- oauth2_token = oauth2_response['access_token']
274
- print(f"āœ… OAuth2 token acquired (expires in: {oauth2_response.get('expires_in', 'unknown')} seconds)")
275
-
276
- ${hasBody ? `# Step 2: Prepare request body
277
- request_body = ${JSON.stringify(operationConfig.sampleBody, null, 8).replace(/"/g, "'")}
278
-
279
- ${operation === 'create_transaction' ? `# Customize with parameters
280
- if amount:
281
- request_body['trData']['amount'] = amount
282
- if currency:
283
- request_body['trData']['currency'] = currency
284
- if reason_l1:
285
- request_body['trData']['reasonL1'] = reason_l1
286
- if reason_l2:
287
- request_body['trData']['reasonL2'] = reason_l2` : ''}
288
-
289
- # Step 3: Create ISV JWT token with OAuth2 token
290
- print("šŸ” Creating ISV JWT token with OAuth2 authentication")
291
- token, body_string = create_isv_jwt_token(
292
- merchant_partner_id,
293
- oauth2_token,
294
- request_body,
295
- use_sandbox
296
- )
297
- headers = get_api_headers(token)
298
-
299
- # Step 4: Make API request
300
- url = f"{base_url}${endpoint}"${needsId ? `.format(transaction_id=transaction_id)` : ''}
301
- print(f"šŸ“” Making ${operationConfig.method} request to: {url}")
302
- response = requests.${operationConfig.method.toLowerCase()}(
303
- url,
304
- data=body_string,
305
- headers=headers
306
- )` : `# Step 2: Create ISV JWT token with OAuth2 token
307
- print("šŸ” Creating ISV JWT token with OAuth2 authentication")
308
- token, _ = create_isv_jwt_token(
309
- merchant_partner_id,
310
- oauth2_token,
311
- None,
312
- use_sandbox
313
- )
314
- headers = get_api_headers(token)
315
-
316
- # Step 3: Make API request
317
- url = f"{base_url}${endpoint}"${needsId ? `.format(transaction_id=transaction_id)` : ''}
318
- print(f"šŸ“” Making ${operationConfig.method} request to: {url}")
319
- response = requests.${operationConfig.method.toLowerCase()}(
320
- url,
321
- headers=headers
322
- )`}
323
-
324
- # Step ${hasBody ? '5' : '4'}: Handle response
325
- if response.status_code in [200, 201]:
326
- result = response.json()
327
- return {
328
- 'success': True,
329
- 'data': result,
330
- 'merchant_partner_id': merchant_partner_id,
331
- 'oauth2_info': {
332
- 'token_type': oauth2_response['token_type'],
333
- 'scope': oauth2_response.get('scope'),
334
- 'expires_in': oauth2_response.get('expires_in')
335
- },
336
- 'request_id': response.headers.get('x-request-id'),
337
- 'timestamp': datetime.utcnow().isoformat()
338
- }
339
- else:
340
- return {
341
- 'success': False,
342
- 'error': {
343
- 'status': response.status_code,
344
- 'message': response.text,
345
- 'details': response.json() if response.headers.get('content-type', '').startswith('application/json') else None
346
- },
347
- 'merchant_partner_id': merchant_partner_id,
348
- 'timestamp': datetime.utcnow().isoformat()
349
- }
350
-
351
- except Exception as e:
352
- return {
353
- 'success': False,
354
- 'error': {
355
- 'message': str(e),
356
- 'type': type(e).__name__
357
- },
358
- 'merchant_partner_id': merchant_partner_id,
359
- 'timestamp': datetime.utcnow().isoformat()
360
- }`);
361
-
362
- // Error handling
363
- sections.push(`# ISV Error Handling
364
- def handle_isv_error(error_response):
365
- """Handle ISV-specific API errors"""
366
- status = error_response.get('status', 0)
367
- message = error_response.get('message', 'Unknown error')
368
-
369
- isv_error_messages = {
370
- 400: f"Bad Request: {message}",
371
- 401: "Authentication failed - check ISV credentials and OAuth2 setup",
372
- 403: "Forbidden - ISV may not have permission for this merchant",
373
- 404: "Resource not found",
374
- 409: "Conflict - resource already exists or invalid state",
375
- 429: "Rate limit exceeded - please retry later",
376
- 500: "Internal server error - please try again"
377
- }
378
-
379
- base_message = isv_error_messages.get(status, f"API Error {status}: {message}")
380
-
381
- # Add ISV-specific guidance
382
- if status == 401:
383
- base_message += "\\nšŸ’” ISV Troubleshooting:"
384
- base_message += "\\n - Verify PAYWARE_PARTNER_ID is your ISV partner ID"
385
- base_message += "\\n - Check OAuth2 client credentials"
386
- base_message += "\\n - Ensure merchant has granted access to your ISV"
387
- elif status == 403:
388
- base_message += "\\nšŸ’” ISV Authorization:"
389
- base_message += "\\n - Merchant may need to authorize your ISV application"
390
- base_message += "\\n - Check OAuth2 scope permissions"
391
- base_message += "\\n - Verify merchant partner ID is correct"
392
-
393
- return base_message`);
394
-
395
- // Example usage
396
- const exampleParams = needsId ? '"MERCHANT123", "pw12345678"' : '"MERCHANT123"';
397
- sections.push(`# Example Usage
398
- if __name__ == "__main__":
399
- print("=== ISV ${operation.replace('_', ' ').title()} Example ===\\n")
400
-
401
- # Example merchant partner ID (replace with actual merchant ID)
402
- merchant_id = "MERCHANT123"
403
-
404
- try:
405
- # Execute the ISV operation
406
- result = ${operation}(${exampleParams})
407
-
408
- if result['success']:
409
- print("āœ… ISV operation successful!")
410
- print(f"šŸ¢ Merchant: {result['merchant_partner_id']}")
411
- print(f"šŸ” OAuth2 Scope: {result['oauth2_info']['scope']}")
412
- print(f"šŸ“‹ Request ID: {result.get('request_id', 'N/A')}")
413
- print("\\nšŸ“Š Response Data:")
414
- print(json.dumps(result['data'], indent=2))
415
- else:
416
- print("āŒ ISV operation failed!")
417
- print(f"šŸ¢ Merchant: {result['merchant_partner_id']}")
418
- error_message = handle_isv_error(result['error'])
419
- print(f"āŒ Error: {error_message}")
420
-
421
- if result['error'].get('details'):
422
- print("\\nšŸ“‹ Error Details:")
423
- print(json.dumps(result['error']['details'], indent=2))
424
-
425
- except Exception as e:
426
- print(f"āŒ Unexpected error: {e}")
427
- print("\\nšŸ’” Common Issues:")
428
- print(" - Check all ISV environment variables are set")
429
- print(" - Verify merchant has authorized your ISV application")
430
- print(" - Ensure OAuth2 credentials are correct")
431
-
432
- print(f"\\nTimestamp: {datetime.utcnow().isoformat()}")
433
- print("\\n" + "="*60)`);
434
-
435
- return sections.join('\n\n');
436
- }
437
-
438
- /**
439
- * Generate Node.js examples for ISV transactions
440
- */
441
- export function generateISVTransactionNodeJS(operation, params = {}) {
442
- const operationConfig = ISVTransactionOperations[operation];
443
-
444
- if (!operationConfig) {
445
- throw new Error(`Unknown operation: ${operation}`);
446
- }
447
-
448
- const sections = [];
449
-
450
- // Header and description
451
- sections.push(`/**
452
- * ${operationConfig.description}
453
- *
454
- * This example demonstrates ISV operations for managing transactions on behalf of merchants.
455
- * Partner Type: ISV (Independent Software Vendor)
456
- * Operation: ${operation}
457
- * Method: ${operationConfig.method}
458
- * Endpoint: ${operationConfig.endpoint}
459
- * Authentication: ISV JWT + OAuth2 Token
460
- */
461
-
462
- // Required dependencies:
463
- // npm install axios jsonwebtoken crypto fs dotenv
464
-
465
- const axios = require('axios');
466
- const jwt = require('jsonwebtoken');
467
- const crypto = require('crypto');
468
- const fs = require('fs');
469
- require('dotenv').config();`);
470
-
471
- // Configuration
472
- sections.push(`// ISV Configuration
473
- function loadISVConfig() {
474
- const config = {
475
- isvPartnerId: process.env.PAYWARE_PARTNER_ID,
476
- sandboxKeyPath: process.env.PAYWARE_SANDBOX_PRIVATE_KEY_PATH,
477
- productionKeyPath: process.env.PAYWARE_PRODUCTION_PRIVATE_KEY_PATH,
478
- oauthClientId: process.env.PAYWARE_OAUTH_CLIENT_ID,
479
- oauthClientSecret: process.env.PAYWARE_OAUTH_CLIENT_SECRET,
480
- defaultMerchantId: process.env.PAYWARE_DEFAULT_MERCHANT_ID,
481
- sandboxUrl: process.env.PAYWARE_SANDBOX_URL || 'https://sandbox.payware.eu/api',
482
- productionUrl: process.env.PAYWARE_PRODUCTION_URL || 'https://api.payware.eu/api'
483
- };
484
-
485
- // Validate required configuration
486
- const requiredVars = ['isvPartnerId', 'oauthClientId', 'oauthClientSecret'];
487
- for (const varName of requiredVars) {
488
- if (!config[varName]) {
489
- const envVar = {
490
- isvPartnerId: 'PAYWARE_PARTNER_ID',
491
- oauthClientId: 'PAYWARE_OAUTH_CLIENT_ID',
492
- oauthClientSecret: 'PAYWARE_OAUTH_CLIENT_SECRET'
493
- }[varName];
494
- throw new Error(\`\${envVar} environment variable is required for ISV operations\`);
495
- }
496
- }
497
-
498
- return config;
499
- }`);
500
-
501
- // OAuth2 functions
502
- sections.push(`// OAuth2 Authentication for ISV
503
- async function getOAuth2Token(merchantPartnerId, useSandbox = true) {
504
- const config = loadISVConfig();
505
- const baseUrl = useSandbox ? config.sandboxUrl : config.productionUrl;
506
-
507
- const tokenData = new URLSearchParams({
508
- grant_type: 'client_credentials',
509
- client_id: config.oauthClientId,
510
- client_secret: config.oauthClientSecret,
511
- scope: \`merchant:\${merchantPartnerId}\`
512
- });
513
-
514
- try {
515
- const response = await axios.post(
516
- \`\${baseUrl}/oauth2/token\`,
517
- tokenData,
518
- {
519
- headers: {
520
- 'Content-Type': 'application/x-www-form-urlencoded'
521
- }
522
- }
523
- );
524
-
525
- return {
526
- accessToken: response.data.access_token,
527
- tokenType: response.data.token_type,
528
- expiresIn: response.data.expires_in,
529
- scope: response.data.scope
530
- };
531
-
532
- } catch (error) {
533
- throw new Error(\`OAuth2 token request failed: \${error.response?.data?.error || error.message}\`);
534
- }
535
- }
536
-
537
- function createISVJWTToken(merchantPartnerId, oauth2Token, requestBody = null, useSandbox = true) {
538
- const config = loadISVConfig();
539
-
540
- // Get private key path based on environment
541
- const privateKeyPath = useSandbox ? config.sandboxKeyPath : config.productionKeyPath;
542
- if (!privateKeyPath) {
543
- const envVar = useSandbox ? 'PAYWARE_SANDBOX_PRIVATE_KEY_PATH' : 'PAYWARE_PRODUCTION_PRIVATE_KEY_PATH';
544
- throw new Error(\`\${envVar} environment variable is required\`);
545
- }
546
-
547
- // Load ISV private key
548
- const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
549
-
550
- // Create JWT header
551
- const header = {
552
- alg: 'RS256',
553
- typ: 'JWT'
554
- };
555
-
556
- // Add content SHA-256 for requests with body
557
- let bodyString = null;
558
- if (requestBody) {
559
- bodyString = createMinimizedJSON(requestBody);
560
- const contentSha256 = crypto
561
- .createHash('sha256')
562
- .update(bodyString, 'utf8')
563
- .digest('base64');
564
- header.contentSha256 = contentSha256;
565
- }
566
-
567
- // Create ISV JWT payload with OAuth2 token
568
- const payload = {
569
- iss: config.isvPartnerId, // ISV Partner ID
570
- aud: merchantPartnerId, // Target Merchant ID
571
- sub: oauth2Token, // OAuth2 access token
572
- iat: Math.floor(Date.now() / 1000)
573
- };
574
-
575
- // Create token
576
- const token = jwt.sign(payload, privateKey, {
577
- algorithm: 'RS256',
578
- header
579
- });
580
-
581
- return { token, bodyString };
582
- }
583
-
584
- function createMinimizedJSON(data) {
585
- // Sort object keys recursively for deterministic output
586
- function sortObject(obj) {
587
- if (Array.isArray(obj)) {
588
- return obj.map(item => sortObject(item));
589
- } else if (obj !== null && typeof obj === 'object') {
590
- const sorted = {};
591
- Object.keys(obj).sort().forEach(key => {
592
- sorted[key] = sortObject(obj[key]);
593
- });
594
- return sorted;
595
- }
596
- return obj;
597
- }
598
-
599
- const sortedData = sortObject(data);
600
- return JSON.stringify(sortedData);
601
- }
602
-
603
- function getAPIHeaders(token) {
604
- return {
605
- 'Authorization': \`Bearer \${token}\`,
606
- 'Content-Type': 'application/json',
607
- 'Api-Version': '1'
608
- };
609
- }`);
610
-
611
- // Main operation function
612
- const needsId = operationConfig.endpoint.includes('{id}');
613
- const hasBody = operationConfig.method !== 'GET' && Object.keys(operationConfig.sampleBody).length > 0;
614
-
615
- const paramList = ['merchantPartnerId', 'useSandbox = true'];
616
- if (needsId) {
617
- paramList.splice(1, 0, 'transactionId');
618
- }
619
-
620
- // Add custom parameters for specific operations
621
- if (operation === 'create_transaction') {
622
- paramList.splice(-1, 0, 'amount = "50.00"', 'currency = "EUR"', 'reasonL1 = "ISV transaction"', 'reasonL2 = null');
623
- }
624
-
625
- const endpoint = operationConfig.endpoint.replace('{id}', '${transactionId}');
626
-
627
- sections.push(`// Main ISV Operation
628
- async function ${operation}(${paramList.join(', ')}) {
629
- /**
630
- * ${operationConfig.description}
631
- */
632
-
633
- try {
634
- const config = loadISVConfig();
635
- const baseUrl = useSandbox ? config.sandboxUrl : config.productionUrl;
636
-
637
- // Step 1: Get OAuth2 token for merchant access
638
- console.log(\`šŸ” Getting OAuth2 token for merchant: \${merchantPartnerId}\`);
639
- const oauth2Response = await getOAuth2Token(merchantPartnerId, useSandbox);
640
- const oauth2Token = oauth2Response.accessToken;
641
- console.log(\`āœ… OAuth2 token acquired (expires in: \${oauth2Response.expiresIn || 'unknown'} seconds)\`);
642
-
643
- ${hasBody ? `// Step 2: Prepare request body
644
- const requestBody = ${JSON.stringify(operationConfig.sampleBody, null, 4)};
645
-
646
- ${operation === 'create_transaction' ? `// Customize with parameters
647
- if (amount) requestBody.trData.amount = amount;
648
- if (currency) requestBody.trData.currency = currency;
649
- if (reasonL1) requestBody.trData.reasonL1 = reasonL1;
650
- if (reasonL2) requestBody.trData.reasonL2 = reasonL2;` : ''}
651
-
652
- // Step 3: Create ISV JWT token with OAuth2 token
653
- console.log('šŸ” Creating ISV JWT token with OAuth2 authentication');
654
- const { token, bodyString } = createISVJWTToken(
655
- merchantPartnerId,
656
- oauth2Token,
657
- requestBody,
658
- useSandbox
659
- );
660
- const headers = getAPIHeaders(token);
661
-
662
- // Step 4: Make API request
663
- const url = \`\${baseUrl}${endpoint}\`;
664
- console.log(\`šŸ“” Making ${operationConfig.method} request to: \${url}\`);
665
- const response = await axios.${operationConfig.method.toLowerCase()}(
666
- url,
667
- bodyString,
668
- {
669
- headers,
670
- transformRequest: [(data) => data]
671
- }
672
- );` : `// Step 2: Create ISV JWT token with OAuth2 token
673
- console.log('šŸ” Creating ISV JWT token with OAuth2 authentication');
674
- const { token } = createISVJWTToken(
675
- merchantPartnerId,
676
- oauth2Token,
677
- null,
678
- useSandbox
679
- );
680
- const headers = getAPIHeaders(token);
681
-
682
- // Step 3: Make API request
683
- const url = \`\${baseUrl}${endpoint}\`;
684
- console.log(\`šŸ“” Making ${operationConfig.method} request to: \${url}\`);
685
- const response = await axios.${operationConfig.method.toLowerCase()}(
686
- url,
687
- { headers }
688
- );`}
689
-
690
- // Step ${hasBody ? '5' : '4'}: Handle successful response
691
- return {
692
- success: true,
693
- data: response.data,
694
- merchantPartnerId,
695
- oauth2Info: {
696
- tokenType: oauth2Response.tokenType,
697
- scope: oauth2Response.scope,
698
- expiresIn: oauth2Response.expiresIn
699
- },
700
- requestId: response.headers['x-request-id'],
701
- timestamp: new Date().toISOString()
702
- };
703
-
704
- } catch (error) {
705
- // Handle errors
706
- return {
707
- success: false,
708
- error: {
709
- message: error.response?.data?.message || error.message,
710
- status: error.response?.status,
711
- details: error.response?.data
712
- },
713
- merchantPartnerId,
714
- timestamp: new Date().toISOString()
715
- };
716
- }
717
- }`);
718
-
719
- // Error handling
720
- sections.push(`// ISV Error Handling
721
- function handleISVError(errorResponse) {
722
- const status = errorResponse.status || 0;
723
- const message = errorResponse.message || 'Unknown error';
724
-
725
- const isvErrorMessages = {
726
- 400: \`Bad Request: \${message}\`,
727
- 401: 'Authentication failed - check ISV credentials and OAuth2 setup',
728
- 403: 'Forbidden - ISV may not have permission for this merchant',
729
- 404: 'Resource not found',
730
- 409: 'Conflict - resource already exists or invalid state',
731
- 429: 'Rate limit exceeded - please retry later',
732
- 500: 'Internal server error - please try again'
733
- };
734
-
735
- let baseMessage = isvErrorMessages[status] || \`API Error \${status}: \${message}\`;
736
-
737
- // Add ISV-specific guidance
738
- if (status === 401) {
739
- baseMessage += '\\nšŸ’” ISV Troubleshooting:';
740
- baseMessage += '\\n - Verify PAYWARE_PARTNER_ID is your ISV partner ID';
741
- baseMessage += '\\n - Check OAuth2 client credentials';
742
- baseMessage += '\\n - Ensure merchant has granted access to your ISV';
743
- } else if (status === 403) {
744
- baseMessage += '\\nšŸ’” ISV Authorization:';
745
- baseMessage += '\\n - Merchant may need to authorize your ISV application';
746
- baseMessage += '\\n - Check OAuth2 scope permissions';
747
- baseMessage += '\\n - Verify merchant partner ID is correct';
748
- }
749
-
750
- return baseMessage;
751
- }`);
752
-
753
- // Example usage
754
- const exampleParams = needsId ? "'MERCHANT123', 'pw12345678'" : "'MERCHANT123'";
755
- sections.push(`// Example Usage
756
- async function main() {
757
- console.log('=== ISV ${operation.replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase())} Example ===\\n');
758
-
759
- // Example merchant partner ID (replace with actual merchant ID)
760
- const merchantId = 'MERCHANT123';
761
-
762
- try {
763
- // Execute the ISV operation
764
- const result = await ${operation}(${exampleParams});
765
-
766
- if (result.success) {
767
- console.log('āœ… ISV operation successful!');
768
- console.log(\`šŸ¢ Merchant: \${result.merchantPartnerId}\`);
769
- console.log(\`šŸ” OAuth2 Scope: \${result.oauth2Info.scope}\`);
770
- console.log(\`šŸ“‹ Request ID: \${result.requestId || 'N/A'}\`);
771
- console.log('\\nšŸ“Š Response Data:');
772
- console.log(JSON.stringify(result.data, null, 2));
773
- } else {
774
- console.log('āŒ ISV operation failed!');
775
- console.log(\`šŸ¢ Merchant: \${result.merchantPartnerId}\`);
776
- const errorMessage = handleISVError(result.error);
777
- console.log(\`āŒ Error: \${errorMessage}\`);
778
-
779
- if (result.error.details) {
780
- console.log('\\nšŸ“‹ Error Details:');
781
- console.log(JSON.stringify(result.error.details, null, 2));
782
- }
783
- }
784
-
785
- } catch (error) {
786
- console.error(\`āŒ Unexpected error: \${error.message}\`);
787
- console.log('\\nšŸ’” Common Issues:');
788
- console.log(' - Check all ISV environment variables are set');
789
- console.log(' - Verify merchant has authorized your ISV application');
790
- console.log(' - Ensure OAuth2 credentials are correct');
791
- }
792
-
793
- console.log(\`\\nTimestamp: \${new Date().toISOString()}\`);
794
- console.log('\\n' + '='.repeat(60));
795
- }
796
-
797
- // Run the example
798
- main();`);
799
-
800
- return sections.join('\n\n');
801
- }
802
-
803
- /**
804
- * Export all ISV transaction examples
805
- */
806
- export const ISVTransactionExamples = {
807
- operations: ISVTransactionOperations,
808
- generators: {
809
- python: generateISVTransactionPython,
810
- nodejs: generateISVTransactionNodeJS,
811
- javascript: generateISVTransactionNodeJS
812
- }
1
+ /**
2
+ * ISV transaction examples for all operations
3
+ */
4
+
5
+ import { PythonGenerator, NodeJSGenerator } from '../common/utils-examples.js';
6
+ import { CommonTemplates } from '../common/helpers.js';
7
+
8
+ /**
9
+ * ISV transaction operations
10
+ */
11
+ export const ISVTransactionOperations = {
12
+ create_transaction: {
13
+ description: 'Create a transaction on behalf of a merchant (ISV operation)',
14
+ endpoint: '/transactions',
15
+ method: 'POST',
16
+ requiresOAuth: true,
17
+ sampleBody: {
18
+ trData: {
19
+ amount: '50.00',
20
+ currency: 'EUR',
21
+ reasonL1: 'ISV managed transaction',
22
+ reasonL2: 'Created on behalf of merchant'
23
+ },
24
+ trOptions: {
25
+ type: 'QR',
26
+ timeToLive: 600
27
+ }
28
+ }
29
+ },
30
+
31
+ get_transaction_status: {
32
+ description: 'Get transaction status on behalf of merchant',
33
+ endpoint: '/transactions/{id}',
34
+ method: 'GET',
35
+ requiresOAuth: true,
36
+ sampleBody: {}
37
+ },
38
+
39
+ cancel_transaction: {
40
+ description: 'Cancel transaction on behalf of merchant',
41
+ endpoint: '/transactions/{id}',
42
+ method: 'PATCH',
43
+ requiresOAuth: true,
44
+ sampleBody: {
45
+ status: 'CANCELLED',
46
+ statusMessage: 'ISV initiated cancellation'
47
+ }
48
+ },
49
+
50
+ process_transaction: {
51
+ description: 'Process transaction on behalf of merchant',
52
+ endpoint: '/transactions/{id}',
53
+ method: 'POST',
54
+ requiresOAuth: true,
55
+ sampleBody: {
56
+ account: 'MERCHANT_ACCOUNT_123',
57
+ friendlyName: 'Merchant Name via ISV'
58
+ }
59
+ },
60
+
61
+ get_transaction_history: {
62
+ description: 'Get transaction history for merchant',
63
+ endpoint: '/transactions/{id}/history',
64
+ method: 'GET',
65
+ requiresOAuth: true,
66
+ sampleBody: {}
67
+ },
68
+
69
+ simulate_callback: {
70
+ description: 'Simulate callback for merchant transaction',
71
+ endpoint: '/transactions/{id}/callback',
72
+ method: 'POST',
73
+ requiresOAuth: true,
74
+ sampleBody: {
75
+ status: 'PROCESSED',
76
+ timestamp: new Date().toISOString()
77
+ }
78
+ }
79
+ };
80
+
81
+ /**
82
+ * Generate Python examples for ISV transactions
83
+ */
84
+ export function generateISVTransactionPython(operation, params = {}) {
85
+ const operationConfig = ISVTransactionOperations[operation];
86
+
87
+ if (!operationConfig) {
88
+ throw new Error(`Unknown operation: ${operation}`);
89
+ }
90
+
91
+ const sections = [];
92
+
93
+ // Header and description
94
+ sections.push(`"""
95
+ ${operationConfig.description}
96
+
97
+ This example demonstrates ISV operations for managing transactions on behalf of merchants.
98
+ Partner Type: ISV (Independent Software Vendor)
99
+ Operation: ${operation}
100
+ Method: ${operationConfig.method}
101
+ Endpoint: ${operationConfig.endpoint}
102
+ Authentication: ISV JWT + OAuth2 Token
103
+ """
104
+
105
+ # Required dependencies:
106
+ # pip install requests pyjwt cryptography python-dotenv
107
+
108
+ import requests
109
+ import jwt
110
+ import json
111
+ import hashlib
112
+ import base64
113
+ import os
114
+ from datetime import datetime
115
+ from dotenv import load_dotenv
116
+
117
+ # Load environment variables
118
+ load_dotenv()`);
119
+
120
+ // Environment setup
121
+ sections.push(`# ISV Environment Configuration
122
+ def load_isv_config():
123
+ """Load ISV configuration from environment"""
124
+ config = {
125
+ 'isv_partner_id': os.getenv('PAYWARE_PARTNER_ID'),
126
+ 'sandbox_key_path': os.getenv('PAYWARE_SANDBOX_PRIVATE_KEY_PATH'),
127
+ 'production_key_path': os.getenv('PAYWARE_PRODUCTION_PRIVATE_KEY_PATH'),
128
+ 'oauth_client_id': os.getenv('PAYWARE_OAUTH_CLIENT_ID'),
129
+ 'oauth_client_secret': os.getenv('PAYWARE_OAUTH_CLIENT_SECRET'),
130
+ 'default_merchant_id': os.getenv('PAYWARE_DEFAULT_MERCHANT_ID'),
131
+ 'sandbox_url': os.getenv('PAYWARE_SANDBOX_URL', 'https://sandbox.payware.eu/api'),
132
+ 'production_url': os.getenv('PAYWARE_PRODUCTION_URL', 'https://api.payware.eu/api')
133
+ }
134
+
135
+ # Validate required configuration
136
+ required_vars = ['isv_partner_id', 'oauth_client_id', 'oauth_client_secret']
137
+ for var in required_vars:
138
+ if not config[var]:
139
+ env_var = {
140
+ 'isv_partner_id': 'PAYWARE_PARTNER_ID',
141
+ 'oauth_client_id': 'PAYWARE_OAUTH_CLIENT_ID',
142
+ 'oauth_client_secret': 'PAYWARE_OAUTH_CLIENT_SECRET'
143
+ }[var]
144
+ raise ValueError(f"{env_var} environment variable is required for ISV operations")
145
+
146
+ return config`);
147
+
148
+ // OAuth2 functions
149
+ sections.push(`# OAuth2 Authentication for ISV
150
+ def get_oauth2_token(merchant_partner_id, use_sandbox=True):
151
+ """Get OAuth2 token for accessing merchant resources"""
152
+ config = load_isv_config()
153
+ base_url = config['sandbox_url'] if use_sandbox else config['production_url']
154
+
155
+ # OAuth2 token request: JSON body, camelCase fields, no scope parameter.
156
+ # clientId is the merchant's partnerId; clientSecret is that merchant's secret, base64-encoded.
157
+ token_data = {
158
+ 'grantType': 'client_credentials',
159
+ 'clientId': merchant_partner_id,
160
+ 'clientSecret': base64.b64encode(config['merchant_secret'].encode()).decode()
161
+ }
162
+
163
+ try:
164
+ # OAuth2 endpoints are served from the host root, not under /api
165
+ response = requests.post(
166
+ f"{base_url.replace('/api', '')}/oauth2/tokens",
167
+ json=token_data,
168
+ headers={'Content-Type': 'application/json'}
169
+ )
170
+
171
+ if response.status_code == 200:
172
+ token_response = response.json()
173
+ return {
174
+ 'access_token': token_response['accessToken'],
175
+ 'token_type': token_response['tokenType'],
176
+ 'status': token_response.get('status'),
177
+ 'expires_in': token_response.get('expiresIn'),
178
+ 'scope': token_response.get('scope')
179
+ }
180
+ else:
181
+ raise Exception(f"OAuth2 token request failed: {response.status_code} - {response.text}")
182
+
183
+ except Exception as e:
184
+ raise Exception(f"OAuth2 authentication error: {str(e)}")
185
+
186
+ def create_isv_jwt_token(merchant_partner_id, oauth2_token, request_body=None, use_sandbox=True):
187
+ """Create ISV JWT token with OAuth2 token for merchant access"""
188
+ config = load_isv_config()
189
+
190
+ # Get private key path based on environment
191
+ private_key_path = config['sandbox_key_path'] if use_sandbox else config['production_key_path']
192
+ if not private_key_path:
193
+ env_var = 'PAYWARE_SANDBOX_PRIVATE_KEY_PATH' if use_sandbox else 'PAYWARE_PRODUCTION_PRIVATE_KEY_PATH'
194
+ raise ValueError(f"{env_var} environment variable is required")
195
+
196
+ # Load ISV private key
197
+ with open(private_key_path, 'r') as f:
198
+ private_key = f.read()
199
+
200
+ # Create JWT header
201
+ header = {
202
+ 'alg': 'RS256',
203
+ 'typ': 'JWT'
204
+ }
205
+
206
+ # Add content SHA-256 for requests with body
207
+ body_string = None
208
+ if request_body:
209
+ body_string = create_minimized_json(request_body)
210
+ content_sha256 = base64.b64encode(
211
+ hashlib.sha256(body_string.encode('utf-8')).digest()
212
+ ).decode('utf-8')
213
+ header['contentSha256'] = content_sha256
214
+
215
+ # Create ISV JWT payload with OAuth2 token
216
+ payload = {
217
+ 'iss': config['isv_partner_id'], # ISV Partner ID
218
+ 'aud': merchant_partner_id, # Target Merchant ID
219
+ 'sub': oauth2_token, # OAuth2 access token
220
+ 'iat': int(datetime.utcnow().timestamp())
221
+ }
222
+
223
+ # Create token
224
+ token = jwt.encode(payload, private_key, algorithm='RS256', headers=header)
225
+ return token, body_string
226
+
227
+ def create_minimized_json(data):
228
+ """Create deterministic minimized JSON for SHA-256 calculation"""
229
+ def sort_dict(obj):
230
+ if isinstance(obj, dict):
231
+ return {k: sort_dict(v) for k, v in sorted(obj.items())}
232
+ elif isinstance(obj, list):
233
+ return [sort_dict(item) for item in obj]
234
+ else:
235
+ return obj
236
+
237
+ sorted_data = sort_dict(data)
238
+ return json.dumps(sorted_data, separators=(',', ':'))
239
+
240
+ def get_api_headers(token):
241
+ """Get standard API headers"""
242
+ return {
243
+ 'Authorization': f'Bearer {token}',
244
+ 'Content-Type': 'application/json',
245
+ 'Api-Version': '1'
246
+ }`);
247
+
248
+ // Main operation function
249
+ const needsId = operationConfig.endpoint.includes('{id}');
250
+ const hasBody = operationConfig.method !== 'GET' && Object.keys(operationConfig.sampleBody).length > 0;
251
+
252
+ const paramList = ['merchant_partner_id', 'use_sandbox=True'];
253
+ if (needsId) {
254
+ paramList.splice(1, 0, 'transaction_id');
255
+ }
256
+
257
+ // Add custom parameters for specific operations
258
+ if (operation === 'create_transaction') {
259
+ paramList.splice(-1, 0, 'amount="50.00"', 'currency="EUR"', 'reason_l1="ISV transaction"', 'reason_l2=None');
260
+ }
261
+
262
+ const endpoint = operationConfig.endpoint.replace('{id}', '{transaction_id}');
263
+
264
+ sections.push(`# Main ISV Operation
265
+ def ${operation}(${paramList.join(', ')}):
266
+ """${operationConfig.description}"""
267
+
268
+ try:
269
+ config = load_isv_config()
270
+ base_url = config['sandbox_url'] if use_sandbox else config['production_url']
271
+
272
+ # Step 1: Get OAuth2 token for merchant access
273
+ print(f"šŸ” Getting OAuth2 token for merchant: {merchant_partner_id}")
274
+ oauth2_response = get_oauth2_token(merchant_partner_id, use_sandbox)
275
+ oauth2_token = oauth2_response['access_token']
276
+ print(f"āœ… OAuth2 token acquired (expires in: {oauth2_response.get('expires_in', 'unknown')} seconds)")
277
+
278
+ ${hasBody ? `# Step 2: Prepare request body
279
+ request_body = ${JSON.stringify(operationConfig.sampleBody, null, 8).replace(/"/g, "'")}
280
+
281
+ ${operation === 'create_transaction' ? `# Customize with parameters
282
+ if amount:
283
+ request_body['trData']['amount'] = amount
284
+ if currency:
285
+ request_body['trData']['currency'] = currency
286
+ if reason_l1:
287
+ request_body['trData']['reasonL1'] = reason_l1
288
+ if reason_l2:
289
+ request_body['trData']['reasonL2'] = reason_l2` : ''}
290
+
291
+ # Step 3: Create ISV JWT token with OAuth2 token
292
+ print("šŸ” Creating ISV JWT token with OAuth2 authentication")
293
+ token, body_string = create_isv_jwt_token(
294
+ merchant_partner_id,
295
+ oauth2_token,
296
+ request_body,
297
+ use_sandbox
298
+ )
299
+ headers = get_api_headers(token)
300
+
301
+ # Step 4: Make API request
302
+ url = f"{base_url}${endpoint}"${needsId ? `.format(transaction_id=transaction_id)` : ''}
303
+ print(f"šŸ“” Making ${operationConfig.method} request to: {url}")
304
+ response = requests.${operationConfig.method.toLowerCase()}(
305
+ url,
306
+ data=body_string,
307
+ headers=headers
308
+ )` : `# Step 2: Create ISV JWT token with OAuth2 token
309
+ print("šŸ” Creating ISV JWT token with OAuth2 authentication")
310
+ token, _ = create_isv_jwt_token(
311
+ merchant_partner_id,
312
+ oauth2_token,
313
+ None,
314
+ use_sandbox
315
+ )
316
+ headers = get_api_headers(token)
317
+
318
+ # Step 3: Make API request
319
+ url = f"{base_url}${endpoint}"${needsId ? `.format(transaction_id=transaction_id)` : ''}
320
+ print(f"šŸ“” Making ${operationConfig.method} request to: {url}")
321
+ response = requests.${operationConfig.method.toLowerCase()}(
322
+ url,
323
+ headers=headers
324
+ )`}
325
+
326
+ # Step ${hasBody ? '5' : '4'}: Handle response
327
+ if response.status_code in [200, 201]:
328
+ result = response.json()
329
+ return {
330
+ 'success': True,
331
+ 'data': result,
332
+ 'merchant_partner_id': merchant_partner_id,
333
+ 'oauth2_info': {
334
+ 'token_type': oauth2_response['token_type'],
335
+ 'scope': oauth2_response.get('scope'),
336
+ 'expires_in': oauth2_response.get('expires_in')
337
+ },
338
+ 'request_id': response.headers.get('x-request-id'),
339
+ 'timestamp': datetime.utcnow().isoformat()
340
+ }
341
+ else:
342
+ return {
343
+ 'success': False,
344
+ 'error': {
345
+ 'status': response.status_code,
346
+ 'message': response.text,
347
+ 'details': response.json() if response.headers.get('content-type', '').startswith('application/json') else None
348
+ },
349
+ 'merchant_partner_id': merchant_partner_id,
350
+ 'timestamp': datetime.utcnow().isoformat()
351
+ }
352
+
353
+ except Exception as e:
354
+ return {
355
+ 'success': False,
356
+ 'error': {
357
+ 'message': str(e),
358
+ 'type': type(e).__name__
359
+ },
360
+ 'merchant_partner_id': merchant_partner_id,
361
+ 'timestamp': datetime.utcnow().isoformat()
362
+ }`);
363
+
364
+ // Error handling
365
+ sections.push(`# ISV Error Handling
366
+ def handle_isv_error(error_response):
367
+ """Handle ISV-specific API errors"""
368
+ status = error_response.get('status', 0)
369
+ message = error_response.get('message', 'Unknown error')
370
+
371
+ isv_error_messages = {
372
+ 400: f"Bad Request: {message}",
373
+ 401: "Authentication failed - check ISV credentials and OAuth2 setup",
374
+ 403: "Forbidden - ISV may not have permission for this merchant",
375
+ 404: "Resource not found",
376
+ 409: "Conflict - resource already exists or invalid state",
377
+ 429: "Rate limit exceeded - please retry later",
378
+ 500: "Internal server error - please try again"
379
+ }
380
+
381
+ base_message = isv_error_messages.get(status, f"API Error {status}: {message}")
382
+
383
+ # Add ISV-specific guidance
384
+ if status == 401:
385
+ base_message += "\\nšŸ’” ISV Troubleshooting:"
386
+ base_message += "\\n - Verify PAYWARE_PARTNER_ID is your ISV partner ID"
387
+ base_message += "\\n - Check OAuth2 client credentials"
388
+ base_message += "\\n - Ensure merchant has granted access to your ISV"
389
+ elif status == 403:
390
+ base_message += "\\nšŸ’” ISV Authorization:"
391
+ base_message += "\\n - Merchant may need to authorize your ISV application"
392
+ base_message += "\\n - Check OAuth2 scope permissions"
393
+ base_message += "\\n - Verify merchant partner ID is correct"
394
+
395
+ return base_message`);
396
+
397
+ // Example usage
398
+ const exampleParams = needsId ? '"MERCHANT123", "pw12345678"' : '"MERCHANT123"';
399
+ sections.push(`# Example Usage
400
+ if __name__ == "__main__":
401
+ print("=== ISV ${operation.replace('_', ' ').title()} Example ===\\n")
402
+
403
+ # Example merchant partner ID (replace with actual merchant ID)
404
+ merchant_id = "MERCHANT123"
405
+
406
+ try:
407
+ # Execute the ISV operation
408
+ result = ${operation}(${exampleParams})
409
+
410
+ if result['success']:
411
+ print("āœ… ISV operation successful!")
412
+ print(f"šŸ¢ Merchant: {result['merchant_partner_id']}")
413
+ print(f"šŸ” OAuth2 Scope: {result['oauth2_info']['scope']}")
414
+ print(f"šŸ“‹ Request ID: {result.get('request_id', 'N/A')}")
415
+ print("\\nšŸ“Š Response Data:")
416
+ print(json.dumps(result['data'], indent=2))
417
+ else:
418
+ print("āŒ ISV operation failed!")
419
+ print(f"šŸ¢ Merchant: {result['merchant_partner_id']}")
420
+ error_message = handle_isv_error(result['error'])
421
+ print(f"āŒ Error: {error_message}")
422
+
423
+ if result['error'].get('details'):
424
+ print("\\nšŸ“‹ Error Details:")
425
+ print(json.dumps(result['error']['details'], indent=2))
426
+
427
+ except Exception as e:
428
+ print(f"āŒ Unexpected error: {e}")
429
+ print("\\nšŸ’” Common Issues:")
430
+ print(" - Check all ISV environment variables are set")
431
+ print(" - Verify merchant has authorized your ISV application")
432
+ print(" - Ensure OAuth2 credentials are correct")
433
+
434
+ print(f"\\nTimestamp: {datetime.utcnow().isoformat()}")
435
+ print("\\n" + "="*60)`);
436
+
437
+ return sections.join('\n\n');
438
+ }
439
+
440
+ /**
441
+ * Generate Node.js examples for ISV transactions
442
+ */
443
+ export function generateISVTransactionNodeJS(operation, params = {}) {
444
+ const operationConfig = ISVTransactionOperations[operation];
445
+
446
+ if (!operationConfig) {
447
+ throw new Error(`Unknown operation: ${operation}`);
448
+ }
449
+
450
+ const sections = [];
451
+
452
+ // Header and description
453
+ sections.push(`/**
454
+ * ${operationConfig.description}
455
+ *
456
+ * This example demonstrates ISV operations for managing transactions on behalf of merchants.
457
+ * Partner Type: ISV (Independent Software Vendor)
458
+ * Operation: ${operation}
459
+ * Method: ${operationConfig.method}
460
+ * Endpoint: ${operationConfig.endpoint}
461
+ * Authentication: ISV JWT + OAuth2 Token
462
+ */
463
+
464
+ // Required dependencies:
465
+ // npm install axios jsonwebtoken crypto fs dotenv
466
+
467
+ const axios = require('axios');
468
+ const jwt = require('jsonwebtoken');
469
+ const crypto = require('crypto');
470
+ const fs = require('fs');
471
+ require('dotenv').config();`);
472
+
473
+ // Configuration
474
+ sections.push(`// ISV Configuration
475
+ function loadISVConfig() {
476
+ const config = {
477
+ isvPartnerId: process.env.PAYWARE_PARTNER_ID,
478
+ sandboxKeyPath: process.env.PAYWARE_SANDBOX_PRIVATE_KEY_PATH,
479
+ productionKeyPath: process.env.PAYWARE_PRODUCTION_PRIVATE_KEY_PATH,
480
+ oauthClientId: process.env.PAYWARE_OAUTH_CLIENT_ID,
481
+ oauthClientSecret: process.env.PAYWARE_OAUTH_CLIENT_SECRET,
482
+ defaultMerchantId: process.env.PAYWARE_DEFAULT_MERCHANT_ID,
483
+ sandboxUrl: process.env.PAYWARE_SANDBOX_URL || 'https://sandbox.payware.eu/api',
484
+ productionUrl: process.env.PAYWARE_PRODUCTION_URL || 'https://api.payware.eu/api'
485
+ };
486
+
487
+ // Validate required configuration
488
+ const requiredVars = ['isvPartnerId', 'oauthClientId', 'oauthClientSecret'];
489
+ for (const varName of requiredVars) {
490
+ if (!config[varName]) {
491
+ const envVar = {
492
+ isvPartnerId: 'PAYWARE_PARTNER_ID',
493
+ oauthClientId: 'PAYWARE_OAUTH_CLIENT_ID',
494
+ oauthClientSecret: 'PAYWARE_OAUTH_CLIENT_SECRET'
495
+ }[varName];
496
+ throw new Error(\`\${envVar} environment variable is required for ISV operations\`);
497
+ }
498
+ }
499
+
500
+ return config;
501
+ }`);
502
+
503
+ // OAuth2 functions
504
+ sections.push(`// OAuth2 Authentication for ISV
505
+ async function getOAuth2Token(merchantPartnerId, useSandbox = true) {
506
+ const config = loadISVConfig();
507
+ const baseUrl = useSandbox ? config.sandboxUrl : config.productionUrl;
508
+
509
+ const tokenData = new URLSearchParams({
510
+ grantType: 'client_credentials',
511
+ clientId: merchantPartnerId,
512
+ clientSecret: Buffer.from(config.merchantSecret).toString('base64')
513
+ });
514
+
515
+ try {
516
+ // JSON body, camelCase fields, no scope - and /oauth2/tokens is served from the host root
517
+ const response = await axios.post(
518
+ \`\${baseUrl.replace('/api', '')}/oauth2/tokens\`,
519
+ tokenData,
520
+ {
521
+ headers: {
522
+ 'Content-Type': 'application/json'
523
+ }
524
+ }
525
+ );
526
+
527
+ return {
528
+ accessToken: response.data.accessToken,
529
+ tokenType: response.data.tokenType,
530
+ status: response.data.status,
531
+ expiresIn: response.data.expiresIn,
532
+ scope: response.data.scope
533
+ };
534
+
535
+ } catch (error) {
536
+ throw new Error(\`OAuth2 token request failed: \${error.response?.data?.error || error.message}\`);
537
+ }
538
+ }
539
+
540
+ function createISVJWTToken(merchantPartnerId, oauth2Token, requestBody = null, useSandbox = true) {
541
+ const config = loadISVConfig();
542
+
543
+ // Get private key path based on environment
544
+ const privateKeyPath = useSandbox ? config.sandboxKeyPath : config.productionKeyPath;
545
+ if (!privateKeyPath) {
546
+ const envVar = useSandbox ? 'PAYWARE_SANDBOX_PRIVATE_KEY_PATH' : 'PAYWARE_PRODUCTION_PRIVATE_KEY_PATH';
547
+ throw new Error(\`\${envVar} environment variable is required\`);
548
+ }
549
+
550
+ // Load ISV private key
551
+ const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
552
+
553
+ // Create JWT header
554
+ const header = {
555
+ alg: 'RS256',
556
+ typ: 'JWT'
557
+ };
558
+
559
+ // Add content SHA-256 for requests with body
560
+ let bodyString = null;
561
+ if (requestBody) {
562
+ bodyString = createMinimizedJSON(requestBody);
563
+ const contentSha256 = crypto
564
+ .createHash('sha256')
565
+ .update(bodyString, 'utf8')
566
+ .digest('base64');
567
+ header.contentSha256 = contentSha256;
568
+ }
569
+
570
+ // Create ISV JWT payload with OAuth2 token
571
+ const payload = {
572
+ iss: config.isvPartnerId, // ISV Partner ID
573
+ aud: merchantPartnerId, // Target Merchant ID
574
+ sub: oauth2Token, // OAuth2 access token
575
+ iat: Math.floor(Date.now() / 1000)
576
+ };
577
+
578
+ // Create token
579
+ const token = jwt.sign(payload, privateKey, {
580
+ algorithm: 'RS256',
581
+ header
582
+ });
583
+
584
+ return { token, bodyString };
585
+ }
586
+
587
+ function createMinimizedJSON(data) {
588
+ // Sort object keys recursively for deterministic output
589
+ function sortObject(obj) {
590
+ if (Array.isArray(obj)) {
591
+ return obj.map(item => sortObject(item));
592
+ } else if (obj !== null && typeof obj === 'object') {
593
+ const sorted = {};
594
+ Object.keys(obj).sort().forEach(key => {
595
+ sorted[key] = sortObject(obj[key]);
596
+ });
597
+ return sorted;
598
+ }
599
+ return obj;
600
+ }
601
+
602
+ const sortedData = sortObject(data);
603
+ return JSON.stringify(sortedData);
604
+ }
605
+
606
+ function getAPIHeaders(token) {
607
+ return {
608
+ 'Authorization': \`Bearer \${token}\`,
609
+ 'Content-Type': 'application/json',
610
+ 'Api-Version': '1'
611
+ };
612
+ }`);
613
+
614
+ // Main operation function
615
+ const needsId = operationConfig.endpoint.includes('{id}');
616
+ const hasBody = operationConfig.method !== 'GET' && Object.keys(operationConfig.sampleBody).length > 0;
617
+
618
+ const paramList = ['merchantPartnerId', 'useSandbox = true'];
619
+ if (needsId) {
620
+ paramList.splice(1, 0, 'transactionId');
621
+ }
622
+
623
+ // Add custom parameters for specific operations
624
+ if (operation === 'create_transaction') {
625
+ paramList.splice(-1, 0, 'amount = "50.00"', 'currency = "EUR"', 'reasonL1 = "ISV transaction"', 'reasonL2 = null');
626
+ }
627
+
628
+ const endpoint = operationConfig.endpoint.replace('{id}', '${transactionId}');
629
+
630
+ sections.push(`// Main ISV Operation
631
+ async function ${operation}(${paramList.join(', ')}) {
632
+ /**
633
+ * ${operationConfig.description}
634
+ */
635
+
636
+ try {
637
+ const config = loadISVConfig();
638
+ const baseUrl = useSandbox ? config.sandboxUrl : config.productionUrl;
639
+
640
+ // Step 1: Get OAuth2 token for merchant access
641
+ console.log(\`šŸ” Getting OAuth2 token for merchant: \${merchantPartnerId}\`);
642
+ const oauth2Response = await getOAuth2Token(merchantPartnerId, useSandbox);
643
+ const oauth2Token = oauth2Response.accessToken;
644
+ console.log(\`āœ… OAuth2 token acquired (expires in: \${oauth2Response.expiresIn || 'unknown'} seconds)\`);
645
+
646
+ ${hasBody ? `// Step 2: Prepare request body
647
+ const requestBody = ${JSON.stringify(operationConfig.sampleBody, null, 4)};
648
+
649
+ ${operation === 'create_transaction' ? `// Customize with parameters
650
+ if (amount) requestBody.trData.amount = amount;
651
+ if (currency) requestBody.trData.currency = currency;
652
+ if (reasonL1) requestBody.trData.reasonL1 = reasonL1;
653
+ if (reasonL2) requestBody.trData.reasonL2 = reasonL2;` : ''}
654
+
655
+ // Step 3: Create ISV JWT token with OAuth2 token
656
+ console.log('šŸ” Creating ISV JWT token with OAuth2 authentication');
657
+ const { token, bodyString } = createISVJWTToken(
658
+ merchantPartnerId,
659
+ oauth2Token,
660
+ requestBody,
661
+ useSandbox
662
+ );
663
+ const headers = getAPIHeaders(token);
664
+
665
+ // Step 4: Make API request
666
+ const url = \`\${baseUrl}${endpoint}\`;
667
+ console.log(\`šŸ“” Making ${operationConfig.method} request to: \${url}\`);
668
+ const response = await axios.${operationConfig.method.toLowerCase()}(
669
+ url,
670
+ bodyString,
671
+ {
672
+ headers,
673
+ transformRequest: [(data) => data]
674
+ }
675
+ );` : `// Step 2: Create ISV JWT token with OAuth2 token
676
+ console.log('šŸ” Creating ISV JWT token with OAuth2 authentication');
677
+ const { token } = createISVJWTToken(
678
+ merchantPartnerId,
679
+ oauth2Token,
680
+ null,
681
+ useSandbox
682
+ );
683
+ const headers = getAPIHeaders(token);
684
+
685
+ // Step 3: Make API request
686
+ const url = \`\${baseUrl}${endpoint}\`;
687
+ console.log(\`šŸ“” Making ${operationConfig.method} request to: \${url}\`);
688
+ const response = await axios.${operationConfig.method.toLowerCase()}(
689
+ url,
690
+ { headers }
691
+ );`}
692
+
693
+ // Step ${hasBody ? '5' : '4'}: Handle successful response
694
+ return {
695
+ success: true,
696
+ data: response.data,
697
+ merchantPartnerId,
698
+ oauth2Info: {
699
+ tokenType: oauth2Response.tokenType,
700
+ scope: oauth2Response.scope,
701
+ expiresIn: oauth2Response.expiresIn
702
+ },
703
+ requestId: response.headers['x-request-id'],
704
+ timestamp: new Date().toISOString()
705
+ };
706
+
707
+ } catch (error) {
708
+ // Handle errors
709
+ return {
710
+ success: false,
711
+ error: {
712
+ message: error.response?.data?.message || error.message,
713
+ status: error.response?.status,
714
+ details: error.response?.data
715
+ },
716
+ merchantPartnerId,
717
+ timestamp: new Date().toISOString()
718
+ };
719
+ }
720
+ }`);
721
+
722
+ // Error handling
723
+ sections.push(`// ISV Error Handling
724
+ function handleISVError(errorResponse) {
725
+ const status = errorResponse.status || 0;
726
+ const message = errorResponse.message || 'Unknown error';
727
+
728
+ const isvErrorMessages = {
729
+ 400: \`Bad Request: \${message}\`,
730
+ 401: 'Authentication failed - check ISV credentials and OAuth2 setup',
731
+ 403: 'Forbidden - ISV may not have permission for this merchant',
732
+ 404: 'Resource not found',
733
+ 409: 'Conflict - resource already exists or invalid state',
734
+ 429: 'Rate limit exceeded - please retry later',
735
+ 500: 'Internal server error - please try again'
736
+ };
737
+
738
+ let baseMessage = isvErrorMessages[status] || \`API Error \${status}: \${message}\`;
739
+
740
+ // Add ISV-specific guidance
741
+ if (status === 401) {
742
+ baseMessage += '\\nšŸ’” ISV Troubleshooting:';
743
+ baseMessage += '\\n - Verify PAYWARE_PARTNER_ID is your ISV partner ID';
744
+ baseMessage += '\\n - Check OAuth2 client credentials';
745
+ baseMessage += '\\n - Ensure merchant has granted access to your ISV';
746
+ } else if (status === 403) {
747
+ baseMessage += '\\nšŸ’” ISV Authorization:';
748
+ baseMessage += '\\n - Merchant may need to authorize your ISV application';
749
+ baseMessage += '\\n - Check OAuth2 scope permissions';
750
+ baseMessage += '\\n - Verify merchant partner ID is correct';
751
+ }
752
+
753
+ return baseMessage;
754
+ }`);
755
+
756
+ // Example usage
757
+ const exampleParams = needsId ? "'MERCHANT123', 'pw12345678'" : "'MERCHANT123'";
758
+ sections.push(`// Example Usage
759
+ async function main() {
760
+ console.log('=== ISV ${operation.replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase())} Example ===\\n');
761
+
762
+ // Example merchant partner ID (replace with actual merchant ID)
763
+ const merchantId = 'MERCHANT123';
764
+
765
+ try {
766
+ // Execute the ISV operation
767
+ const result = await ${operation}(${exampleParams});
768
+
769
+ if (result.success) {
770
+ console.log('āœ… ISV operation successful!');
771
+ console.log(\`šŸ¢ Merchant: \${result.merchantPartnerId}\`);
772
+ console.log(\`šŸ” OAuth2 Scope: \${result.oauth2Info.scope}\`);
773
+ console.log(\`šŸ“‹ Request ID: \${result.requestId || 'N/A'}\`);
774
+ console.log('\\nšŸ“Š Response Data:');
775
+ console.log(JSON.stringify(result.data, null, 2));
776
+ } else {
777
+ console.log('āŒ ISV operation failed!');
778
+ console.log(\`šŸ¢ Merchant: \${result.merchantPartnerId}\`);
779
+ const errorMessage = handleISVError(result.error);
780
+ console.log(\`āŒ Error: \${errorMessage}\`);
781
+
782
+ if (result.error.details) {
783
+ console.log('\\nšŸ“‹ Error Details:');
784
+ console.log(JSON.stringify(result.error.details, null, 2));
785
+ }
786
+ }
787
+
788
+ } catch (error) {
789
+ console.error(\`āŒ Unexpected error: \${error.message}\`);
790
+ console.log('\\nšŸ’” Common Issues:');
791
+ console.log(' - Check all ISV environment variables are set');
792
+ console.log(' - Verify merchant has authorized your ISV application');
793
+ console.log(' - Ensure OAuth2 credentials are correct');
794
+ }
795
+
796
+ console.log(\`\\nTimestamp: \${new Date().toISOString()}\`);
797
+ console.log('\\n' + '='.repeat(60));
798
+ }
799
+
800
+ // Run the example
801
+ main();`);
802
+
803
+ return sections.join('\n\n');
804
+ }
805
+
806
+ /**
807
+ * Export all ISV transaction examples
808
+ */
809
+ export const ISVTransactionExamples = {
810
+ operations: ISVTransactionOperations,
811
+ generators: {
812
+ python: generateISVTransactionPython,
813
+ nodejs: generateISVTransactionNodeJS,
814
+ javascript: generateISVTransactionNodeJS
815
+ }
813
816
  };