@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.
- package/README.md +5 -0
- package/package.json +69 -66
- package/src/auth/sandbox-setup.js +186 -186
- package/src/isv/index.js +29 -1
- package/src/isv/invitations/index.js +471 -0
- package/src/isv/oauth2/obtain-token.js +19 -8
- package/src/isv/oauth2/token-helper.js +5 -2
- package/src/isv/poi/cancel-price.js +2 -10
- package/src/isv/poi/create.js +19 -0
- package/src/isv/poi/get-poi.js +2 -10
- package/src/isv/poi/get-qrcode.js +2 -10
- package/src/isv/poi/get-status.js +2 -10
- package/src/isv/poi/index.js +5 -0
- package/src/isv/poi/list-pois.js +2 -10
- package/src/isv/poi/set-price.js +217 -225
- package/src/isv/products/create-product.js +313 -313
- package/src/isv/products/update-product.js +264 -264
- package/src/isv/reference/index.js +249 -0
- package/src/isv/shops/index.js +22 -0
- package/src/isv/transactions/cancel-transaction.js +41 -10
- package/src/isv/transactions/create-transaction.js +20 -4
- package/src/isv/transactions/get-status.js +18 -11
- package/src/isv/transactions/process-transaction.js +160 -42
- package/src/isv/transactions/simulate-callback.js +128 -109
- package/src/isv/transactions/transaction-history.js +86 -90
- package/src/merchant/data/generate-report.js +19 -1
- package/src/merchant/index.js +21 -1
- package/src/merchant/logs/index.js +366 -0
- package/src/merchant/products/create-product.js +287 -287
- package/src/merchant/products/update-product.js +281 -281
- package/src/merchant/shops/index.js +18 -0
- package/src/merchant/transactions/cancel-transaction.js +10 -13
- package/src/merchant/transactions/create-transaction.js +88 -8
- package/src/merchant/transactions/get-status.js +40 -11
- package/src/merchant/transactions/process-transaction.js +2 -10
- package/src/merchant/transactions/simulate-callback.js +68 -9
- package/src/merchant/transactions/transaction-history.js +275 -257
- package/src/payment-institution/transactions/create-pi-transaction.js +2 -10
- package/src/payment-institution/transactions/finalize-transaction.js +333 -340
- package/src/payment-institution/transactions/get-transaction-history.js +2 -10
- package/src/payment-institution/transactions/get-transaction-status.js +38 -11
- package/src/payment-institution/transactions/process-pi-transaction.js +2 -10
- package/src/payment-institution/transactions/simulate-pi-callback.js +49 -9
- package/src/payment-institution/transactions/soundbite-transaction.js +2 -10
- package/src/shared/api-errors.js +188 -0
- package/src/shared/auth-headers.js +62 -0
- package/src/shared/data/data-api.js +11 -10
- package/src/shared/deep-links/api.js +5 -4
- package/src/shared/logs/logs-api.js +242 -0
- package/src/shared/poi/poi-create-api.js +419 -0
- package/src/shared/products/products-api.js +17 -16
- package/src/shared/shops/shops-api.js +155 -0
- package/src/tools/advanced-code-generator.js +2255 -2246
- package/src/utils/code-examples/common/auth-examples.js +1484 -1481
- package/src/utils/code-examples/common/helpers.js +7 -4
- package/src/utils/code-examples/isv/oauth2.js +458 -600
- package/src/utils/code-examples/isv/transactions.js +815 -812
- package/MCP-SETUP.md +0 -190
|
@@ -1,600 +1,458 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ISV OAuth2 authentication flow examples
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
result
|
|
114
|
-
|
|
115
|
-
print(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
print("
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
console.
|
|
373
|
-
|
|
374
|
-
console.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
console.
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
+
}
|