@payware/mcp-server 1.3.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/README.md +18 -0
- package/package.json +7 -9
- package/src/auth/rsa-keys.js +12 -6
- package/src/auth/sandbox-setup.js +2 -0
- package/src/config/toolsets.js +104 -0
- package/src/core/auth/jwt-factory.js +4 -1
- package/src/core/auth/jwt-token.js +4 -0
- package/src/index.js +10 -6
- package/src/isv/data/index.js +15 -14
- package/src/isv/deep-links/delete-product-link.js +3 -2
- package/src/isv/deep-links/delete-transaction-link.js +3 -2
- package/src/isv/deep-links/get-product-link.js +8 -6
- package/src/isv/deep-links/get-transaction-link.js +8 -6
- package/src/isv/index.js +6 -0
- package/src/isv/invitations/index.js +5 -0
- package/src/isv/oauth2/get-token-info.js +3 -0
- package/src/isv/oauth2/obtain-token.js +3 -0
- package/src/isv/oauth2/token-helper.js +2 -0
- package/src/isv/poi/cancel-price.js +2 -0
- package/src/isv/poi/get-poi.js +2 -0
- package/src/isv/poi/get-qrcode.js +40 -9
- package/src/isv/poi/get-status.js +2 -0
- package/src/isv/poi/list-pois.js +2 -0
- package/src/isv/poi/set-price.js +2 -0
- package/src/isv/products/audios.js +8 -2
- package/src/isv/products/create-product.js +2 -0
- package/src/isv/products/delete-product.js +2 -0
- package/src/isv/products/get-product-image.js +2 -0
- package/src/isv/products/get-product.js +2 -0
- package/src/isv/products/list-products.js +2 -0
- package/src/isv/products/schedules.js +6 -0
- package/src/isv/products/update-product.js +2 -0
- package/src/isv/reference/index.js +2 -0
- package/src/isv/transactions/cancel-transaction.js +2 -0
- package/src/isv/transactions/create-transaction.js +14 -2
- package/src/isv/transactions/get-status.js +3 -1
- package/src/isv/transactions/process-transaction.js +14 -2
- package/src/isv/transactions/simulate-callback.js +2 -0
- package/src/isv/transactions/transaction-history.js +2 -0
- package/src/merchant/data/export-report.js +2 -0
- package/src/merchant/data/generate-report.js +2 -0
- package/src/merchant/data/get-export-list.js +2 -0
- package/src/merchant/data/get-report-requests.js +2 -0
- package/src/merchant/data/get-report-status.js +2 -0
- package/src/merchant/data/index.js +15 -10
- package/src/merchant/data/list-reports.js +2 -0
- package/src/merchant/deep-links/delete-product-link.js +3 -2
- package/src/merchant/deep-links/delete-transaction-link.js +3 -2
- package/src/merchant/deep-links/get-product-link.js +8 -6
- package/src/merchant/deep-links/get-transaction-link.js +8 -6
- package/src/merchant/index.js +7 -1
- package/src/merchant/logs/index.js +4 -0
- package/src/merchant/products/audios.js +15 -14
- package/src/merchant/products/create-product.js +3 -2
- package/src/merchant/products/delete-product.js +3 -2
- package/src/merchant/products/get-product-image.js +3 -2
- package/src/merchant/products/get-product.js +3 -2
- package/src/merchant/products/list-products.js +3 -2
- package/src/merchant/products/schedules.js +11 -10
- package/src/merchant/products/update-product.js +3 -2
- package/src/merchant/transactions/cancel-transaction.js +4 -3
- package/src/merchant/transactions/create-transaction.js +14 -2
- package/src/merchant/transactions/get-status.js +4 -3
- package/src/merchant/transactions/process-transaction.js +17 -6
- package/src/merchant/transactions/simulate-callback.js +3 -0
- package/src/merchant/transactions/transaction-history.js +5 -4
- package/src/payment-institution/data/index.js +15 -14
- package/src/payment-institution/deep-links/delete-transaction-link.js +3 -2
- package/src/payment-institution/deep-links/get-transaction-link.js +8 -6
- package/src/payment-institution/transactions/create-pi-transaction.js +15 -4
- package/src/payment-institution/transactions/finalize-transaction.js +7 -4
- package/src/payment-institution/transactions/get-transaction-history.js +7 -4
- package/src/payment-institution/transactions/get-transaction-status.js +15 -12
- package/src/payment-institution/transactions/process-pi-transaction.js +19 -8
- package/src/payment-institution/transactions/simulate-pi-callback.js +3 -0
- package/src/payment-institution/transactions/soundbite-transaction.js +5 -4
- package/src/proxy-server.js +25 -0
- package/src/shared/placements/api.js +51 -0
- package/src/shared/placements/tools.js +519 -0
- package/src/shared/poi/poi-create-api.js +3 -0
- package/src/shared/products/products-api.js +36 -25
- package/src/shared/shops/shops-api.js +2 -0
- package/src/shared/tool-annotations.js +53 -0
- package/src/tools/advanced-code-generator.js +3 -0
- package/src/utils/format-request.js +2 -0
- package/src/utils/json-formatter.js +2 -0
- package/src/utils/server-info.js +2 -0
- package/src/utils/code-examples.js +0 -646
|
@@ -3,6 +3,7 @@ import { createJWTToken } from '../../core/auth/jwt-token.js';
|
|
|
3
3
|
import { createMinimizedJSON } from '../../core/utils/json-serializer.js';
|
|
4
4
|
import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
|
|
5
5
|
import { apiErrorResult } from '../../shared/api-errors.js';
|
|
6
|
+
import { CREATES } from '../../shared/tool-annotations.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Create a payment institution transaction via payware API
|
|
@@ -36,6 +37,7 @@ export async function createPITransaction({
|
|
|
36
37
|
barBarHeight,
|
|
37
38
|
barFontSize,
|
|
38
39
|
barHumanReadableLocation,
|
|
40
|
+
idempotencyKey,
|
|
39
41
|
useSandbox = true
|
|
40
42
|
}) {
|
|
41
43
|
if (!role || !['SRC', 'DST'].includes(role)) {
|
|
@@ -121,7 +123,8 @@ export async function createPITransaction({
|
|
|
121
123
|
const headers = {
|
|
122
124
|
'Authorization': `Bearer ${tokenData.token}`,
|
|
123
125
|
'Content-Type': 'application/json',
|
|
124
|
-
'Api-Version': '1' // Required: current API version
|
|
126
|
+
'Api-Version': '1', // Required: current API version
|
|
127
|
+
...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
|
|
125
128
|
};
|
|
126
129
|
|
|
127
130
|
try {
|
|
@@ -138,6 +141,7 @@ export async function createPITransaction({
|
|
|
138
141
|
success: true,
|
|
139
142
|
transaction: response.data,
|
|
140
143
|
requestId: response.headers['x-request-id'],
|
|
144
|
+
replayed: response.headers?.['idempotent-replayed'] === 'true',
|
|
141
145
|
timestamp: new Date().toISOString()
|
|
142
146
|
};
|
|
143
147
|
} catch (error) {
|
|
@@ -150,6 +154,7 @@ export async function createPITransaction({
|
|
|
150
154
|
*/
|
|
151
155
|
export const createPITransactionTool = {
|
|
152
156
|
name: "payware_operations_create_transaction",
|
|
157
|
+
annotations: CREATES,
|
|
153
158
|
description: `Create a new payware transaction as a payment institution. Payment institutions must specify their role and account details.
|
|
154
159
|
|
|
155
160
|
**Payment Institution Features:**
|
|
@@ -183,6 +188,11 @@ export const createPITransactionTool = {
|
|
|
183
188
|
inputSchema: {
|
|
184
189
|
type: "object",
|
|
185
190
|
properties: {
|
|
191
|
+
idempotencyKey: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Optional. Sent as the Idempotency-Key header. Use one value per payment (a UUID is typical) and the same value on every retry of it: a retry with the same key and body within 24 hours returns the transaction the first call created instead of a new one, and the result reports replayed: true. The same key with a different body is refused (422 ERR_IDEMPOTENCY_KEY_REUSED); a retry while the first call still runs gets 409 ERR_IDEMPOTENCY_KEY_IN_PROGRESS.",
|
|
194
|
+
pattern: "^[\\x21-\\x7E]{1,255}$"
|
|
195
|
+
},
|
|
186
196
|
role: {
|
|
187
197
|
type: "string",
|
|
188
198
|
enum: ["SRC", "DST"],
|
|
@@ -245,8 +255,7 @@ export const createPITransactionTool = {
|
|
|
245
255
|
},
|
|
246
256
|
privateKey: {
|
|
247
257
|
type: "string",
|
|
248
|
-
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
249
|
-
default: getPrivateKeySafe(true)
|
|
258
|
+
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
250
259
|
},
|
|
251
260
|
passbackParams: {
|
|
252
261
|
type: "string",
|
|
@@ -347,6 +356,7 @@ export const createPITransactionTool = {
|
|
|
347
356
|
barBarHeight,
|
|
348
357
|
barFontSize,
|
|
349
358
|
barHumanReadableLocation,
|
|
359
|
+
idempotencyKey,
|
|
350
360
|
useSandbox = true
|
|
351
361
|
} = args;
|
|
352
362
|
|
|
@@ -396,6 +406,7 @@ export const createPITransactionTool = {
|
|
|
396
406
|
barBarHeight,
|
|
397
407
|
barFontSize,
|
|
398
408
|
barHumanReadableLocation,
|
|
409
|
+
idempotencyKey,
|
|
399
410
|
useSandbox
|
|
400
411
|
});
|
|
401
412
|
|
|
@@ -437,7 +448,7 @@ ${JSON.stringify(result.transaction, null, 2)}
|
|
|
437
448
|
**Next Steps:**
|
|
438
449
|
1. Save the transaction ID: \`${result.transaction.transactionId || 'N/A'}\`
|
|
439
450
|
2. ${role === 'SRC' ? 'Share transaction ID with payee for processing' : 'Wait for payer to process this transaction'}
|
|
440
|
-
3. Use \`
|
|
451
|
+
3. Use \`payware_operations_get_transaction_status\` to check status
|
|
441
452
|
4. Monitor callback notifications (if callback URL provided)
|
|
442
453
|
5. ${role === 'SRC' ? 'Complete payment through payware interface' : 'Handle incoming payment processing'}`
|
|
443
454
|
}]
|
|
@@ -3,6 +3,7 @@ import { createJWTToken } from '../../core/auth/jwt-token.js';
|
|
|
3
3
|
import { createMinimizedJSON } from '../../core/utils/json-serializer.js';
|
|
4
4
|
import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
|
|
5
5
|
import { apiErrorResult } from '../../shared/api-errors.js';
|
|
6
|
+
import { COMMITS } from '../../shared/tool-annotations.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Finalize a transaction as a payment institution via payware API
|
|
@@ -98,6 +99,7 @@ export async function finalizeTransaction({
|
|
|
98
99
|
*/
|
|
99
100
|
export const finalizeTransactionTool = {
|
|
100
101
|
name: "payware_operations_finalize_transaction",
|
|
102
|
+
annotations: COMMITS,
|
|
101
103
|
description: `Finalize a transaction as a payment institution. This updates the transaction status after execution in your payment network.
|
|
102
104
|
|
|
103
105
|
**Payment Institution Finalization:**
|
|
@@ -164,8 +166,7 @@ export const finalizeTransactionTool = {
|
|
|
164
166
|
},
|
|
165
167
|
privateKey: {
|
|
166
168
|
type: "string",
|
|
167
|
-
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
168
|
-
default: getPrivateKeySafe()
|
|
169
|
+
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
169
170
|
},
|
|
170
171
|
useSandbox: {
|
|
171
172
|
type: "boolean",
|
|
@@ -233,6 +234,8 @@ export const finalizeTransactionTool = {
|
|
|
233
234
|
privateKey,
|
|
234
235
|
useSandbox
|
|
235
236
|
});
|
|
237
|
+
// The success text quotes the endpoint. The helper above keeps its own baseUrl, so it needs one here.
|
|
238
|
+
const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
|
|
236
239
|
|
|
237
240
|
if (result.success) {
|
|
238
241
|
const statusEmojis = {
|
|
@@ -277,7 +280,7 @@ ${JSON.stringify(result.transaction, null, 2)}
|
|
|
277
280
|
${status === 'CONFIRMED' ?
|
|
278
281
|
`1. ✅ Payment completed successfully
|
|
279
282
|
2. Transaction funds should be processed in your network
|
|
280
|
-
3. Use \`
|
|
283
|
+
3. Use \`payware_operations_get_transaction_history\` to retrieve details later` :
|
|
281
284
|
`1. ⚠️ Payment not completed due to: ${statusMessage || status}
|
|
282
285
|
2. Transaction marked as ${status} in payware system
|
|
283
286
|
3. All parties notified via callbacks`
|
|
@@ -310,7 +313,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
|
|
|
310
313
|
**Timestamp:** ${result.timestamp}
|
|
311
314
|
|
|
312
315
|
**Common Finalization Issues:**
|
|
313
|
-
1. **ERR_MISSING_TRANSACTION**: Transaction ID not found
|
|
316
|
+
1. **ERR_MISSING_TRANSACTION**: Transaction ID not found. **ERR_TRANSACTION_EXPIRED** (also 404): the time to live ran out before the finalize arrived, and the transaction is closed as unpaid
|
|
314
317
|
2. **ERR_TRANSACTION_NOT_PROCESSED**: Transaction hasn't been processed yet
|
|
315
318
|
3. **ERR_ALREADY_PROCESSED**: Transaction already finalized
|
|
316
319
|
4. **ERR_INVALID_STATUS**: Invalid status value provided
|
|
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|
|
2
2
|
import { createJWTToken } from '../../core/auth/jwt-token.js';
|
|
3
3
|
import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
|
|
4
4
|
import { apiErrorResult } from '../../shared/api-errors.js';
|
|
5
|
+
import { READS } from '../../shared/tool-annotations.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Get transaction history via payware API (completed/expired transactions)
|
|
@@ -54,6 +55,7 @@ export async function getPITransactionHistory({
|
|
|
54
55
|
*/
|
|
55
56
|
export const getPITransactionHistoryTool = {
|
|
56
57
|
name: "payware_operations_get_transaction_history",
|
|
58
|
+
annotations: READS,
|
|
57
59
|
description: `Get the complete history of a completed or expired payware transaction as a payment institution.
|
|
58
60
|
|
|
59
61
|
**Use Cases:**
|
|
@@ -86,8 +88,7 @@ export const getPITransactionHistoryTool = {
|
|
|
86
88
|
},
|
|
87
89
|
privateKey: {
|
|
88
90
|
type: "string",
|
|
89
|
-
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
90
|
-
default: getPrivateKeySafe()
|
|
91
|
+
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
91
92
|
},
|
|
92
93
|
useSandbox: {
|
|
93
94
|
type: "boolean",
|
|
@@ -124,6 +125,8 @@ export const getPITransactionHistoryTool = {
|
|
|
124
125
|
privateKey,
|
|
125
126
|
useSandbox
|
|
126
127
|
});
|
|
128
|
+
// The success text quotes the endpoint. The helper above keeps its own baseUrl, so it needs one here.
|
|
129
|
+
const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
|
|
127
130
|
|
|
128
131
|
if (result.success) {
|
|
129
132
|
const tx = result.transaction;
|
|
@@ -235,7 +238,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
|
|
|
235
238
|
1. **ERR_MISSING_TRANSACTION (404)**: Transaction not found in history
|
|
236
239
|
2. **HTTP 429 (Too Many Requests)**: API is throttled, implement retry logic
|
|
237
240
|
3. **Authentication Issues**: Verify JWT token and partner credentials
|
|
238
|
-
4. **Transaction Still Active**: Use \`
|
|
241
|
+
4. **Transaction Still Active**: Use \`payware_operations_get_transaction_status\` for active transactions
|
|
239
242
|
|
|
240
243
|
**Troubleshooting:**
|
|
241
244
|
1. Verify transaction ID is correct: 10 characters total, starts with 'pw' (standard), 'pr' (product), or 'ps' (soundbite)
|
|
@@ -251,7 +254,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
|
|
|
251
254
|
- Avoid excessive polling of this endpoint
|
|
252
255
|
|
|
253
256
|
**Alternative Actions:**
|
|
254
|
-
- If transaction is still active: Use \`
|
|
257
|
+
- If transaction is still active: Use \`payware_operations_get_transaction_status\`
|
|
255
258
|
- If rate limited: Wait and retry after a delay
|
|
256
259
|
- If transaction not found: Verify the transaction ID exists`
|
|
257
260
|
}]
|
|
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|
|
2
2
|
import { createJWTToken } from '../../core/auth/jwt-token.js';
|
|
3
3
|
import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
|
|
4
4
|
import { apiErrorResult } from '../../shared/api-errors.js';
|
|
5
|
+
import { READS } from '../../shared/tool-annotations.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Get transaction status via payware API (active transactions)
|
|
@@ -54,6 +55,7 @@ export async function getPITransactionStatus({
|
|
|
54
55
|
*/
|
|
55
56
|
export const getPITransactionStatusTool = {
|
|
56
57
|
name: "payware_operations_get_transaction_status",
|
|
58
|
+
annotations: READS,
|
|
57
59
|
description: `Get the current status of an active payware transaction as a financial institution.
|
|
58
60
|
|
|
59
61
|
**Use Cases:**
|
|
@@ -96,7 +98,7 @@ Four rules a payer-facing app must follow:
|
|
|
96
98
|
4. **A missing field is usually a plan boundary, not an error.** \`imageUrl\` and \`termsUrl\` are
|
|
97
99
|
Standard and above; \`termsText\` and the descriptions are universal.
|
|
98
100
|
|
|
99
|
-
For a \`pr
|
|
101
|
+
For a \`pr\`, \`pl\` or \`ps\` identifier the values are resolved live (product -> shop -> merchant fallback, then
|
|
100
102
|
plan gating); for the resulting \`pw\` transaction they are the snapshot frozen at creation, so what
|
|
101
103
|
you get always reflects what the payer could have been shown at scan time.
|
|
102
104
|
|
|
@@ -105,14 +107,14 @@ strings at the smallest unit their own currency can be paid in - not always two
|
|
|
105
107
|
**the value to echo back unchanged at finalize**; \`feeFixed\` and \`feeRate\` are informational, and
|
|
106
108
|
recomputing the fee from them is rejected with ERR_FEE_MISMATCH.
|
|
107
109
|
|
|
108
|
-
**Note:** This endpoint only returns active transactions. Use \`
|
|
110
|
+
**Note:** This endpoint only returns active transactions. Use \`payware_operations_get_transaction_history\` for completed/expired transactions.`,
|
|
109
111
|
inputSchema: {
|
|
110
112
|
type: "object",
|
|
111
113
|
properties: {
|
|
112
114
|
transactionId: {
|
|
113
115
|
type: "string",
|
|
114
|
-
description: "Transaction ID to check status. Format: 2-char prefix + 8-char ID. Supports: 'pw' (standard), 'pr' (product), 'ps' (soundbite)",
|
|
115
|
-
pattern: "^(pw|pr|ps)[0-9A-Za-z]{8}$"
|
|
116
|
+
description: "Transaction ID to check status. Format: 2-char prefix + 8-char ID. Supports: 'pw' (standard), 'pr' (product), 'pl' (placement: pays as its product, and the payment is attributed to the placement), 'ps' (soundbite)",
|
|
117
|
+
pattern: "^(pw|pr|pl|ps)[0-9A-Za-z]{8}$"
|
|
116
118
|
},
|
|
117
119
|
partnerId: {
|
|
118
120
|
type: "string",
|
|
@@ -121,8 +123,7 @@ recomputing the fee from them is rejected with ERR_FEE_MISMATCH.
|
|
|
121
123
|
},
|
|
122
124
|
privateKey: {
|
|
123
125
|
type: "string",
|
|
124
|
-
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
125
|
-
default: getPrivateKeySafe()
|
|
126
|
+
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
126
127
|
},
|
|
127
128
|
useSandbox: {
|
|
128
129
|
type: "boolean",
|
|
@@ -159,6 +160,8 @@ recomputing the fee from them is rejected with ERR_FEE_MISMATCH.
|
|
|
159
160
|
privateKey,
|
|
160
161
|
useSandbox
|
|
161
162
|
});
|
|
163
|
+
// The success text quotes the endpoint. The helper above keeps its own baseUrl, so it needs one here.
|
|
164
|
+
const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
|
|
162
165
|
|
|
163
166
|
if (result.success) {
|
|
164
167
|
const tx = result.transaction;
|
|
@@ -209,8 +212,8 @@ ${JSON.stringify(result.transaction, null, 2)}
|
|
|
209
212
|
**Next Steps:**
|
|
210
213
|
1. ${!tx.payerAccount ? 'Transaction waiting for payer to process' : 'Transaction processed, waiting for finalization'}
|
|
211
214
|
2. ${tx.timeToLive && parseInt(tx.timeToLive) < 60 ? '⚠️ Transaction expires soon!' : 'Monitor remaining TTL'}
|
|
212
|
-
3. Use \`
|
|
213
|
-
4. Use \`
|
|
215
|
+
3. Use \`payware_operations_process_transaction\` if you need to process this transaction
|
|
216
|
+
4. Use \`payware_operations_finalize_transaction\` to confirm/decline after processing
|
|
214
217
|
|
|
215
218
|
**⚠️ Transaction State:**
|
|
216
219
|
- This is an **active** transaction
|
|
@@ -240,18 +243,18 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
|
|
|
240
243
|
|
|
241
244
|
**Common Issues:**
|
|
242
245
|
1. **ERR_MISSING_TRANSACTION (404)**: Transaction not found, expired, or already completed
|
|
243
|
-
2. **Invalid Transaction ID**: Check transaction ID format (10 chars: 'pw'/'pr'/'ps' + 8 chars)
|
|
246
|
+
2. **Invalid Transaction ID**: Check transaction ID format (10 chars: 'pw'/'pr'/'pl'/'ps' + 8 chars)
|
|
244
247
|
3. **Authentication Issues**: Verify JWT token and partner credentials
|
|
245
248
|
|
|
246
249
|
**Troubleshooting:**
|
|
247
|
-
1. Verify transaction ID is correct: 10 characters total, starts with 'pw' (standard), 'pr' (product), or 'ps' (soundbite)
|
|
250
|
+
1. Verify transaction ID is correct: 10 characters total, starts with 'pw' (standard), 'pr' (product), 'pl' (placement) or 'ps' (soundbite)
|
|
248
251
|
2. Check if transaction has expired or been completed
|
|
249
|
-
3. Use \`
|
|
252
|
+
3. Use \`payware_operations_get_transaction_history\` for completed/expired transactions
|
|
250
253
|
4. Verify your partner credentials and API access
|
|
251
254
|
5. Ensure transaction was created or you have access to it
|
|
252
255
|
|
|
253
256
|
**Alternative Actions:**
|
|
254
|
-
- If transaction is completed: Use \`
|
|
257
|
+
- If transaction is completed: Use \`payware_operations_get_transaction_history\`
|
|
255
258
|
- If transaction ID is wrong: Verify the correct transaction ID
|
|
256
259
|
- If access issues: Check your payment institution credentials`
|
|
257
260
|
}]
|
|
@@ -3,6 +3,7 @@ import { createJWTToken } from '../../core/auth/jwt-token.js';
|
|
|
3
3
|
import { createMinimizedJSON } from '../../core/utils/json-serializer.js';
|
|
4
4
|
import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
|
|
5
5
|
import { apiErrorResult } from '../../shared/api-errors.js';
|
|
6
|
+
import { COMMITS } from '../../shared/tool-annotations.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Process a transaction as a payment institution via payware API
|
|
@@ -27,6 +28,7 @@ export async function processPITransaction({
|
|
|
27
28
|
deliveryAddress,
|
|
28
29
|
// Payment method for POI transactions (Payment Choice Architecture)
|
|
29
30
|
paymentMethod,
|
|
31
|
+
idempotencyKey,
|
|
30
32
|
useSandbox = true
|
|
31
33
|
}) {
|
|
32
34
|
if (!transactionId) {
|
|
@@ -84,7 +86,8 @@ export async function processPITransaction({
|
|
|
84
86
|
const headers = {
|
|
85
87
|
'Authorization': `Bearer ${tokenData.token}`,
|
|
86
88
|
'Content-Type': 'application/json',
|
|
87
|
-
'Api-Version': '1'
|
|
89
|
+
'Api-Version': '1', // Required: current API version
|
|
90
|
+
...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
|
|
88
91
|
};
|
|
89
92
|
|
|
90
93
|
try {
|
|
@@ -100,6 +103,7 @@ export async function processPITransaction({
|
|
|
100
103
|
success: true,
|
|
101
104
|
transaction: response.data,
|
|
102
105
|
requestId: response.headers['x-request-id'],
|
|
106
|
+
replayed: response.headers?.['idempotent-replayed'] === 'true',
|
|
103
107
|
timestamp: new Date().toISOString()
|
|
104
108
|
};
|
|
105
109
|
} catch (error) {
|
|
@@ -112,6 +116,7 @@ export async function processPITransaction({
|
|
|
112
116
|
*/
|
|
113
117
|
export const processPITransactionTool = {
|
|
114
118
|
name: "payware_operations_process_transaction",
|
|
119
|
+
annotations: COMMITS,
|
|
115
120
|
description: `Process a transaction as a payment institution. Payment institutions can process transactions created by others and must provide account/friendlyName details.
|
|
116
121
|
|
|
117
122
|
**Use Cases:**
|
|
@@ -146,10 +151,15 @@ export const processPITransactionTool = {
|
|
|
146
151
|
inputSchema: {
|
|
147
152
|
type: "object",
|
|
148
153
|
properties: {
|
|
154
|
+
idempotencyKey: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Optional. Sent as the Idempotency-Key header. Use one value per payment (a UUID is typical) and the same value on every retry of it: a retry with the same key and body within 24 hours returns the transaction the first call created instead of a new one, and the result reports replayed: true. The same key with a different body is refused (422 ERR_IDEMPOTENCY_KEY_REUSED); a retry while the first call still runs gets 409 ERR_IDEMPOTENCY_KEY_IN_PROGRESS.",
|
|
157
|
+
pattern: "^[\\x21-\\x7E]{1,255}$"
|
|
158
|
+
},
|
|
149
159
|
transactionId: {
|
|
150
160
|
type: "string",
|
|
151
|
-
description: "Transaction ID to process. Format: 2-char prefix + 8-char ID. Supports: 'pw' (standard), 'pr' (product), 'ps' (soundbite)",
|
|
152
|
-
pattern: "^(pw|pr|ps)[0-9A-Za-z]{8}$"
|
|
161
|
+
description: "Transaction ID to process. Format: 2-char prefix + 8-char ID. Supports: 'pw' (standard), 'pr' (product), 'pl' (placement: pays as its product, and the payment is attributed to the placement), 'ps' (soundbite)",
|
|
162
|
+
pattern: "^(pw|pr|pl|ps)[0-9A-Za-z]{8}$"
|
|
153
163
|
},
|
|
154
164
|
account: {
|
|
155
165
|
type: "string",
|
|
@@ -205,8 +215,7 @@ export const processPITransactionTool = {
|
|
|
205
215
|
},
|
|
206
216
|
privateKey: {
|
|
207
217
|
type: "string",
|
|
208
|
-
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
209
|
-
default: getPrivateKeySafe()
|
|
218
|
+
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
210
219
|
},
|
|
211
220
|
// Payment method for POI transactions
|
|
212
221
|
paymentMethod: {
|
|
@@ -293,6 +302,7 @@ export const processPITransactionTool = {
|
|
|
293
302
|
privateKey = getPrivateKeySafe(args.useSandbox ?? true),
|
|
294
303
|
paymentMethod,
|
|
295
304
|
deliveryAddress,
|
|
305
|
+
idempotencyKey,
|
|
296
306
|
useSandbox = true
|
|
297
307
|
} = args;
|
|
298
308
|
|
|
@@ -339,6 +349,7 @@ export const processPITransactionTool = {
|
|
|
339
349
|
privateKey,
|
|
340
350
|
paymentMethod,
|
|
341
351
|
deliveryAddress,
|
|
352
|
+
idempotencyKey,
|
|
342
353
|
useSandbox
|
|
343
354
|
});
|
|
344
355
|
|
|
@@ -373,7 +384,7 @@ ${JSON.stringify(result.transaction, null, 2)}
|
|
|
373
384
|
1. **TRANSACTION_PROCESSED** callback will be sent to counterparty
|
|
374
385
|
2. Wait for callback notification to your URL (if provided)
|
|
375
386
|
3. Monitor transaction status for completion
|
|
376
|
-
4. Use \`
|
|
387
|
+
4. Use \`payware_operations_finalize_transaction\` to confirm/decline the transaction
|
|
377
388
|
5. Handle final TRANSACTION_FINALIZED callbacks
|
|
378
389
|
|
|
379
390
|
**⚠️ Important:**
|
|
@@ -403,7 +414,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
|
|
|
403
414
|
**Timestamp:** ${result.timestamp}
|
|
404
415
|
|
|
405
416
|
**Common Payment Institution Issues:**
|
|
406
|
-
1. **ERR_MISSING_TRANSACTION**: Transaction ID not found
|
|
417
|
+
1. **ERR_MISSING_TRANSACTION**: Transaction ID not found. **ERR_TRANSACTION_EXPIRED** (also 404): its time to live has run out and it is closed as unpaid
|
|
407
418
|
2. **ERR_ALREADY_PROCESSED**: Transaction already processed
|
|
408
419
|
3. **ERR_MISSING_PARTNER_ACCOUNT**: Account parameter missing or empty
|
|
409
420
|
4. **ERR_MISSING_FRIENDLY_NAME**: Friendly name parameter missing
|
|
@@ -412,7 +423,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
|
|
|
412
423
|
7. **ERR_SPECIFYING_CURRENCY_NOT_ALLOWED**: Cannot change currency on merchant transactions
|
|
413
424
|
|
|
414
425
|
**Troubleshooting:**
|
|
415
|
-
1. Verify transaction ID is correct: 10 characters total, starts with 'pw' (standard), 'pr' (product), or 'ps' (soundbite)
|
|
426
|
+
1. Verify transaction ID is correct: 10 characters total, starts with 'pw' (standard), 'pr' (product), 'pl' (placement) or 'ps' (soundbite)
|
|
416
427
|
2. Ensure account and friendlyName are provided
|
|
417
428
|
3. Check currency matches original transaction currency
|
|
418
429
|
4. Verify callback URL is provided (especially for payee role)
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* @param {Object} options - Additional options
|
|
11
11
|
* @returns {Object} Mock callback payload
|
|
12
12
|
*/
|
|
13
|
+
import { LOCAL } from '../../shared/tool-annotations.js';
|
|
14
|
+
|
|
13
15
|
export function generateMockPICallback(transactionId, callbackType = 'TRANSACTION_PROCESSED', options = {}) {
|
|
14
16
|
const {
|
|
15
17
|
amount = '57.00',
|
|
@@ -146,6 +148,7 @@ export async function simulatePICallbackDelivery(callbackUrl, payload) {
|
|
|
146
148
|
*/
|
|
147
149
|
export const simulatePICallbackTool = {
|
|
148
150
|
name: "payware_operations_simulate_callback",
|
|
151
|
+
annotations: LOCAL,
|
|
149
152
|
description: "Simulate payment institution callback scenarios for testing webhook handling. Payment institutions receive TRANSACTION_PROCESSED when merchant processes their transaction, and TRANSACTION_FINALIZED when transaction is completed.",
|
|
150
153
|
inputSchema: {
|
|
151
154
|
type: "object",
|
|
@@ -4,6 +4,7 @@ import { createJWTToken } from '../../core/auth/jwt-token.js';
|
|
|
4
4
|
import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
|
|
5
5
|
import { readFileSync } from 'fs';
|
|
6
6
|
import { apiErrorResult } from '../../shared/api-errors.js';
|
|
7
|
+
import { CREATES } from '../../shared/tool-annotations.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Get transaction information using Soundbite file via payware API
|
|
@@ -73,6 +74,7 @@ export async function getSoundbiteTransaction({
|
|
|
73
74
|
*/
|
|
74
75
|
export const soundbiteTransactionTool = {
|
|
75
76
|
name: "payware_operations_soundbite_transaction",
|
|
77
|
+
annotations: CREATES,
|
|
76
78
|
description: `Get transaction information using a Soundbite SDK output file. This allows payment institutions to process transactions using audio-based identifiers.
|
|
77
79
|
|
|
78
80
|
**Soundbite Integration:**
|
|
@@ -105,8 +107,7 @@ export const soundbiteTransactionTool = {
|
|
|
105
107
|
},
|
|
106
108
|
privateKey: {
|
|
107
109
|
type: "string",
|
|
108
|
-
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
109
|
-
default: getPrivateKeySafe()
|
|
110
|
+
description: "RSA private key for JWT token creation. Uses environment-specific private key as default."
|
|
110
111
|
},
|
|
111
112
|
useSandbox: {
|
|
112
113
|
type: "boolean",
|
|
@@ -199,8 +200,8 @@ ${JSON.stringify(result.transaction, null, 2)}
|
|
|
199
200
|
1. Transaction successfully identified from soundbite file
|
|
200
201
|
2. Use transaction ID: \`${tx.transactionId || 'N/A'}\` for further operations
|
|
201
202
|
3. ${!tx.payerAccount ? 'Process this transaction if you are the payer' : 'Transaction ready for finalization'}
|
|
202
|
-
4. Use \`
|
|
203
|
-
5. Use \`
|
|
203
|
+
4. Use \`payware_operations_process_transaction\` to process payment
|
|
204
|
+
5. Use \`payware_operations_finalize_transaction\` to confirm/decline
|
|
204
205
|
|
|
205
206
|
**♿ Accessibility Note:**
|
|
206
207
|
- Soundbite provides audio-based alternative to QR/barcode scanning
|
package/src/proxy-server.js
CHANGED
|
@@ -33,6 +33,31 @@ class PaywareMCPProxy {
|
|
|
33
33
|
const allowedOrigins = (process.env.PAYWARE_MCP_PROXY_ORIGINS
|
|
34
34
|
|| `http://localhost:${this.port},http://127.0.0.1:${this.port}`)
|
|
35
35
|
.split(',').map((o) => o.trim()).filter(Boolean);
|
|
36
|
+
|
|
37
|
+
// DNS rebinding and cross-site requests. The CORS allowlist below decides which pages may READ a
|
|
38
|
+
// response; it never refuses a request. A page whose hostname has been rebound to 127.0.0.1 is
|
|
39
|
+
// same-origin with this server, so CORS does not apply to it at all, and a cross-site form POST
|
|
40
|
+
// needs no preflight - either way the route runs and signs as the partner. Two checks refuse them:
|
|
41
|
+
// Host must be a name this server answers to (a rebound page sends its own hostname there), and an
|
|
42
|
+
// Origin, which browsers send on every POST, must be allowlisted. Clients that are not browsers -
|
|
43
|
+
// curl, the bridge - send no Origin and pass. PAYWARE_MCP_PROXY_HOSTS (comma-separated host:port)
|
|
44
|
+
// widens the Host list deliberately.
|
|
45
|
+
const bindHost = process.env.PAYWARE_MCP_PROXY_HOST || '127.0.0.1';
|
|
46
|
+
const allowedHosts = new Set((process.env.PAYWARE_MCP_PROXY_HOSTS
|
|
47
|
+
|| `localhost:${this.port},127.0.0.1:${this.port},${bindHost}:${this.port}`)
|
|
48
|
+
.split(',').map((h) => h.trim().toLowerCase()).filter(Boolean));
|
|
49
|
+
this.app.use((req, res, next) => {
|
|
50
|
+
const host = (req.headers.host || '').toLowerCase();
|
|
51
|
+
if (!allowedHosts.has(host)) {
|
|
52
|
+
return res.status(403).json({ error: 'Forbidden', message: `Host ${host || '(none)'} is not allowed` });
|
|
53
|
+
}
|
|
54
|
+
const origin = req.headers.origin;
|
|
55
|
+
if (origin !== undefined && !allowedOrigins.includes(origin)) {
|
|
56
|
+
return res.status(403).json({ error: 'Forbidden', message: `Origin ${origin} is not allowed` });
|
|
57
|
+
}
|
|
58
|
+
next();
|
|
59
|
+
});
|
|
60
|
+
|
|
36
61
|
this.app.use(cors({
|
|
37
62
|
origin: allowedOrigins,
|
|
38
63
|
credentials: true
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { createMinimizedJSON } from '../../core/utils/json-serializer.js';
|
|
3
|
+
import { getSandboxUrl, getProductionUrl } from '../../config/env.js';
|
|
4
|
+
import { createAuthHeaders } from '../auth-headers.js';
|
|
5
|
+
import { apiErrorResult } from '../api-errors.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One call to the campaigns, placements and airings API, as a merchant or as an ISV acting for one.
|
|
9
|
+
*
|
|
10
|
+
* Built on the shared `createAuthHeaders`, so the on-behalf JWT (`aud` = the merchant, `sub` = its
|
|
11
|
+
* OAuth2 token) and the content hash come from the same code every newer tool uses. The body is
|
|
12
|
+
* serialized once and that exact string is both hashed and sent; `transformRequest` stops axios from
|
|
13
|
+
* serializing it a second time.
|
|
14
|
+
*
|
|
15
|
+
* Never throws for an HTTP error: it returns `apiErrorResult`, whose `details` keeps the response
|
|
16
|
+
* body, which is where a refused replace-all lists its rows.
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} params
|
|
19
|
+
* @param {string} params.method HTTP verb
|
|
20
|
+
* @param {string} params.path path under the API root, ids already encoded
|
|
21
|
+
* @param {Object} [params.query] query parameters; undefined values are left out
|
|
22
|
+
* @param {*} [params.body] request body, serialized deterministically
|
|
23
|
+
* @param {boolean} [params.binary] read the response as bytes (images)
|
|
24
|
+
* @param {Object} params.auth from the role: partnerType, credentials and useSandbox
|
|
25
|
+
*/
|
|
26
|
+
export async function placementsRequest({ method, path, query, body, binary = false, auth }) {
|
|
27
|
+
const minimizedBody = body === undefined ? undefined : createMinimizedJSON(body);
|
|
28
|
+
const headers = await createAuthHeaders(auth.partnerType, { ...auth, requestBody: minimizedBody });
|
|
29
|
+
const baseUrl = auth.useSandbox ? getSandboxUrl() : getProductionUrl();
|
|
30
|
+
const params = query && Object.fromEntries(Object.entries(query).filter(([, v]) => v !== undefined && v !== null));
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await axios({
|
|
34
|
+
method,
|
|
35
|
+
url: `${baseUrl}${path}`,
|
|
36
|
+
headers,
|
|
37
|
+
...(params && Object.keys(params).length > 0 && { params }),
|
|
38
|
+
...(minimizedBody !== undefined && { data: minimizedBody, transformRequest: [(data) => data] }),
|
|
39
|
+
...(binary && { responseType: 'arraybuffer' })
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
success: true,
|
|
43
|
+
status: response.status,
|
|
44
|
+
data: response.data,
|
|
45
|
+
contentType: response.headers['content-type'],
|
|
46
|
+
requestId: response.headers['x-request-id']
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return apiErrorResult(error);
|
|
50
|
+
}
|
|
51
|
+
}
|