@payware/mcp-server 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payware/mcp-server",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "mcpName": "io.github.payware/mcp-server",
5
5
  "description": "Official MCP server for payware payment API integration",
6
6
  "main": "src/index.js",
@@ -37,6 +37,7 @@ export async function createTransaction({
37
37
  barBarHeight,
38
38
  barFontSize,
39
39
  barHumanReadableLocation,
40
+ idempotencyKey,
40
41
  useSandbox = true
41
42
  }) {
42
43
  // ISV-specific validation
@@ -132,13 +133,17 @@ export async function createTransaction({
132
133
  headers: {
133
134
  'Authorization': `Bearer ${jwtData.token}`,
134
135
  'Content-Type': 'application/json',
135
- 'Api-Version': '1'
136
+ 'Api-Version': '1',
137
+ ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
136
138
  },
137
139
  // Tell axios to send the string as-is, don't serialize it again
138
140
  transformRequest: [(data) => data]
139
141
  });
140
142
 
141
- return response.data;
143
+ // A retry answered from the first call (Idempotency-Key) says so in a header, not in the body.
144
+ return response.headers?.['idempotent-replayed'] === 'true'
145
+ ? { ...response.data, replayed: true }
146
+ : response.data;
142
147
  } catch (error) {
143
148
  throw describeApiError(error, 'call the payware API');
144
149
  }
@@ -166,11 +171,13 @@ now returns 403, check the merchant's plan first.
166
171
  answers **403 ERR_SHOP_NOT_IN_SCOPE**, and \`payware_shops_list\` returns only the in-scope ones.
167
172
  Omitting \`shop\` uses the merchant's default shop, which may itself be out of scope.
168
173
 
169
- ℹ️ **Producer attribution does not apply here.** \`producerPartnerId\` / \`terminalId\` /
170
- \`terminalManufacturer\` are accepted on direct merchant calls only. Whoever made the API call owns the
171
- sale, so an ISV call is an ISV sale even when the ISV's software is running on a hardware producer's
172
- terminal. Do not try to pass them through - use \`payware_operations_create_transaction\` from the
173
- merchant role if the terminal producer is meant to be attributed.
174
+ ℹ️ **Producer attribution does not apply here.** Whoever made the API call owns the sale, so an ISV
175
+ call is an ISV sale even when the ISV's software is running on a hardware producer's terminal, and the
176
+ producer earns nothing from it. This tool therefore does not send \`producerPartnerId\` /
177
+ \`terminalId\` / \`terminalManufacturer\`. The server does accept them on an ISV call - it checks
178
+ them (an unrecognised producer is a 400) and records them for provenance - but they never move the
179
+ sale to the producer. Use \`payware_operations_create_transaction\` from the merchant role if the
180
+ terminal producer is meant to be attributed.
174
181
 
175
182
  **Transaction Types:**
176
183
  - PLAIN: Basic transaction without visual codes
@@ -200,6 +207,11 @@ merchant role if the terminal producer is meant to be attributed.
200
207
  type: 'object',
201
208
  required: ['merchantPartnerId', 'oauth2Token', 'reasonL1'],
202
209
  properties: {
210
+ idempotencyKey: {
211
+ type: "string",
212
+ 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.",
213
+ pattern: "^[\\x21-\\x7E]{1,255}$"
214
+ },
203
215
  // ISV Authentication
204
216
  merchantPartnerId: {
205
217
  type: 'string',
@@ -40,6 +40,7 @@ export async function processTransaction({
40
40
  paymentMethod,
41
41
  merchantPartnerId,
42
42
  oauth2Token,
43
+ idempotencyKey,
43
44
  useSandbox = true
44
45
  }) {
45
46
  if (!transactionId) {
@@ -109,13 +110,17 @@ export async function processTransaction({
109
110
  headers: {
110
111
  'Authorization': `Bearer ${jwtData.token}`,
111
112
  'Content-Type': 'application/json',
112
- 'Api-Version': '1'
113
+ 'Api-Version': '1',
114
+ ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
113
115
  },
114
116
  // Tell axios to send the string as-is, don't serialize it again
115
117
  transformRequest: [(data) => data]
116
118
  });
117
119
 
118
- return response.data;
120
+ // A retry answered from the first call (Idempotency-Key) says so in a header, not in the body.
121
+ return response.headers?.['idempotent-replayed'] === 'true'
122
+ ? { ...response.data, replayed: true }
123
+ : response.data;
119
124
  } catch (error) {
120
125
  throw describeApiError(error, 'call the payware API');
121
126
  }
@@ -148,6 +153,11 @@ payware returns on the response is the value to echo back at finalize, unchanged
148
153
  type: 'object',
149
154
  required: ['transactionId', 'amount', 'currency', 'reasonL1', 'merchantPartnerId', 'oauth2Token'],
150
155
  properties: {
156
+ idempotencyKey: {
157
+ type: "string",
158
+ 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.",
159
+ pattern: "^[\\x21-\\x7E]{1,255}$"
160
+ },
151
161
  transactionId: {
152
162
  type: 'string',
153
163
  description: "The customer's transaction to process, e.g. \"pw7e4rCToG\""
@@ -214,7 +214,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
214
214
  **Timestamp:** ${result.timestamp}
215
215
 
216
216
  **Common Issues:**
217
- 1. **ERR_MISSING_TRANSACTION**: Transaction not found or expired
217
+ 1. **ERR_MISSING_TRANSACTION**: Transaction not found. **ERR_TRANSACTION_EXPIRED** (also 404): its time to live has run out and it is already closed as unpaid
218
218
  2. **ERR_ALREADY_PROCESSED**: Transaction already processed, cannot cancel
219
219
  3. **ERR_UNAUTHORIZED_OPERATION**: Only transaction creator can cancel
220
220
  4. **ERR_INVALID_STATUS_MESSAGE**: Status message invalid or too long
@@ -40,6 +40,7 @@ export async function createTransaction({
40
40
  barBarHeight,
41
41
  barFontSize,
42
42
  barHumanReadableLocation,
43
+ idempotencyKey,
43
44
  useSandbox = true
44
45
  }) {
45
46
  if (!reasonL1) {
@@ -142,7 +143,8 @@ export async function createTransaction({
142
143
  const headers = {
143
144
  'Authorization': `Bearer ${tokenData.token}`,
144
145
  'Content-Type': 'application/json',
145
- 'Api-Version': '1' // Required: current API version
146
+ 'Api-Version': '1', // Required: current API version
147
+ ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
146
148
  };
147
149
 
148
150
  try {
@@ -160,6 +162,7 @@ export async function createTransaction({
160
162
  success: true,
161
163
  transaction: response.data,
162
164
  requestId: response.headers['x-request-id'],
165
+ replayed: response.headers?.['idempotent-replayed'] === 'true',
163
166
  timestamp: new Date().toISOString()
164
167
  };
165
168
  } catch (error) {
@@ -255,7 +258,9 @@ agreement with payware. Three fields, all optional:
255
258
  characters. payware records it and never interprets it: no format, no uniqueness enforcement, and
256
259
  nothing about pricing or attribution depends on it.
257
260
  - \`terminalManufacturer\` - device manufacturer as the terminal reports it. Max 64 chars, no control
258
- characters. Recorded for provenance; a mismatch is surfaced in settlement review, never blocked.
261
+ characters. Recorded for provenance. payware's internal attribution review counts each producer's
262
+ sales by reported manufacturer, so a build running on another vendor's hardware stands out; it is
263
+ never blocked.
259
264
 
260
265
  Two things worth knowing:
261
266
  1. **It never changes what the merchant is charged.** The producer's share comes out of payware's own
@@ -269,6 +274,11 @@ ISV's software is running on that producer's hardware.`,
269
274
  inputSchema: {
270
275
  type: "object",
271
276
  properties: {
277
+ idempotencyKey: {
278
+ type: "string",
279
+ 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.",
280
+ pattern: "^[\\x21-\\x7E]{1,255}$"
281
+ },
272
282
  type: {
273
283
  type: "string",
274
284
  enum: ["PLAIN", "QR", "BARCODE"],
@@ -446,6 +456,7 @@ ISV's software is running on that producer's hardware.`,
446
456
  barBarHeight,
447
457
  barFontSize,
448
458
  barHumanReadableLocation,
459
+ idempotencyKey,
449
460
  useSandbox = true
450
461
  } = args;
451
462
 
@@ -490,6 +501,7 @@ ISV's software is running on that producer's hardware.`,
490
501
  barBarHeight,
491
502
  barFontSize,
492
503
  barHumanReadableLocation,
504
+ idempotencyKey,
493
505
  useSandbox
494
506
  });
495
507
 
@@ -25,6 +25,7 @@ export async function processTransaction({
25
25
  paymentMethod,
26
26
  partnerId,
27
27
  privateKey,
28
+ idempotencyKey,
28
29
  useSandbox = true
29
30
  }) {
30
31
  if (!transactionId) {
@@ -80,7 +81,8 @@ export async function processTransaction({
80
81
  const headers = {
81
82
  'Authorization': `Bearer ${tokenData.token}`,
82
83
  'Content-Type': 'application/json',
83
- 'Api-Version': '1' // Required: current API version
84
+ 'Api-Version': '1', // Required: current API version
85
+ ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
84
86
  };
85
87
 
86
88
  try {
@@ -96,6 +98,7 @@ export async function processTransaction({
96
98
  success: true,
97
99
  transaction: response.data,
98
100
  requestId: response.headers['x-request-id'],
101
+ replayed: response.headers?.['idempotent-replayed'] === 'true',
99
102
  timestamp: new Date().toISOString()
100
103
  };
101
104
  } catch (error) {
@@ -138,6 +141,11 @@ export const processTransactionTool = {
138
141
  type: "object",
139
142
  additionalProperties: false,
140
143
  properties: {
144
+ idempotencyKey: {
145
+ type: "string",
146
+ 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.",
147
+ pattern: "^[\\x21-\\x7E]{1,255}$"
148
+ },
141
149
  transactionId: {
142
150
  type: "string",
143
151
  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)",
@@ -236,6 +244,7 @@ export const processTransactionTool = {
236
244
  paymentMethod,
237
245
  partnerId = getPartnerIdSafe(),
238
246
  privateKey = getPrivateKeySafe(args.useSandbox ?? true),
247
+ idempotencyKey,
239
248
  useSandbox = true
240
249
  } = args;
241
250
 
@@ -274,6 +283,7 @@ export const processTransactionTool = {
274
283
  paymentMethod,
275
284
  partnerId,
276
285
  privateKey,
286
+ idempotencyKey,
277
287
  useSandbox
278
288
  });
279
289
 
@@ -334,7 +344,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
334
344
  **Timestamp:** ${result.timestamp}
335
345
 
336
346
  **Common Issues:**
337
- 1. **ERR_MISSING_TRANSACTION**: Transaction ID not found or expired
347
+ 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
338
348
  2. **ERR_UNKNOWN_ROLE**: Transaction generated by payee (can't process own transaction)
339
349
  3. **ERR_ALREADY_PROCESSED**: Transaction already processed
340
350
  4. **ERR_SPECIFYING_AMOUNT_NOT_ALLOWED**: Amount exceeds transaction limit
@@ -37,6 +37,7 @@ export async function createPITransaction({
37
37
  barBarHeight,
38
38
  barFontSize,
39
39
  barHumanReadableLocation,
40
+ idempotencyKey,
40
41
  useSandbox = true
41
42
  }) {
42
43
  if (!role || !['SRC', 'DST'].includes(role)) {
@@ -122,7 +123,8 @@ export async function createPITransaction({
122
123
  const headers = {
123
124
  'Authorization': `Bearer ${tokenData.token}`,
124
125
  'Content-Type': 'application/json',
125
- 'Api-Version': '1' // Required: current API version
126
+ 'Api-Version': '1', // Required: current API version
127
+ ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
126
128
  };
127
129
 
128
130
  try {
@@ -139,6 +141,7 @@ export async function createPITransaction({
139
141
  success: true,
140
142
  transaction: response.data,
141
143
  requestId: response.headers['x-request-id'],
144
+ replayed: response.headers?.['idempotent-replayed'] === 'true',
142
145
  timestamp: new Date().toISOString()
143
146
  };
144
147
  } catch (error) {
@@ -185,6 +188,11 @@ export const createPITransactionTool = {
185
188
  inputSchema: {
186
189
  type: "object",
187
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
+ },
188
196
  role: {
189
197
  type: "string",
190
198
  enum: ["SRC", "DST"],
@@ -348,6 +356,7 @@ export const createPITransactionTool = {
348
356
  barBarHeight,
349
357
  barFontSize,
350
358
  barHumanReadableLocation,
359
+ idempotencyKey,
351
360
  useSandbox = true
352
361
  } = args;
353
362
 
@@ -397,6 +406,7 @@ export const createPITransactionTool = {
397
406
  barBarHeight,
398
407
  barFontSize,
399
408
  barHumanReadableLocation,
409
+ idempotencyKey,
400
410
  useSandbox
401
411
  });
402
412
 
@@ -313,7 +313,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
313
313
  **Timestamp:** ${result.timestamp}
314
314
 
315
315
  **Common Finalization Issues:**
316
- 1. **ERR_MISSING_TRANSACTION**: Transaction ID not found or expired
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
317
317
  2. **ERR_TRANSACTION_NOT_PROCESSED**: Transaction hasn't been processed yet
318
318
  3. **ERR_ALREADY_PROCESSED**: Transaction already finalized
319
319
  4. **ERR_INVALID_STATUS**: Invalid status value provided
@@ -28,6 +28,7 @@ export async function processPITransaction({
28
28
  deliveryAddress,
29
29
  // Payment method for POI transactions (Payment Choice Architecture)
30
30
  paymentMethod,
31
+ idempotencyKey,
31
32
  useSandbox = true
32
33
  }) {
33
34
  if (!transactionId) {
@@ -85,7 +86,8 @@ export async function processPITransaction({
85
86
  const headers = {
86
87
  'Authorization': `Bearer ${tokenData.token}`,
87
88
  'Content-Type': 'application/json',
88
- 'Api-Version': '1' // Required: current API version
89
+ 'Api-Version': '1', // Required: current API version
90
+ ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey })
89
91
  };
90
92
 
91
93
  try {
@@ -101,6 +103,7 @@ export async function processPITransaction({
101
103
  success: true,
102
104
  transaction: response.data,
103
105
  requestId: response.headers['x-request-id'],
106
+ replayed: response.headers?.['idempotent-replayed'] === 'true',
104
107
  timestamp: new Date().toISOString()
105
108
  };
106
109
  } catch (error) {
@@ -148,6 +151,11 @@ export const processPITransactionTool = {
148
151
  inputSchema: {
149
152
  type: "object",
150
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
+ },
151
159
  transactionId: {
152
160
  type: "string",
153
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)",
@@ -294,6 +302,7 @@ export const processPITransactionTool = {
294
302
  privateKey = getPrivateKeySafe(args.useSandbox ?? true),
295
303
  paymentMethod,
296
304
  deliveryAddress,
305
+ idempotencyKey,
297
306
  useSandbox = true
298
307
  } = args;
299
308
 
@@ -340,6 +349,7 @@ export const processPITransactionTool = {
340
349
  privateKey,
341
350
  paymentMethod,
342
351
  deliveryAddress,
352
+ idempotencyKey,
343
353
  useSandbox
344
354
  });
345
355
 
@@ -404,7 +414,7 @@ ${JSON.stringify(result.error.details || result.error, null, 2)}
404
414
  **Timestamp:** ${result.timestamp}
405
415
 
406
416
  **Common Payment Institution Issues:**
407
- 1. **ERR_MISSING_TRANSACTION**: Transaction ID not found or expired
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
408
418
  2. **ERR_ALREADY_PROCESSED**: Transaction already processed
409
419
  3. **ERR_MISSING_PARTNER_ACCOUNT**: Account parameter missing or empty
410
420
  4. **ERR_MISSING_FRIENDLY_NAME**: Friendly name parameter missing