@payware/mcp-server 1.3.2 → 1.4.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 (93) hide show
  1. package/.env.example +4 -0
  2. package/README.md +18 -0
  3. package/package.json +7 -9
  4. package/src/auth/rsa-keys.js +12 -6
  5. package/src/auth/sandbox-setup.js +2 -0
  6. package/src/config/toolsets.js +104 -0
  7. package/src/core/auth/jwt-factory.js +4 -1
  8. package/src/core/auth/jwt-token.js +4 -0
  9. package/src/index.js +10 -6
  10. package/src/isv/data/index.js +15 -14
  11. package/src/isv/deep-links/delete-product-link.js +3 -2
  12. package/src/isv/deep-links/delete-transaction-link.js +3 -2
  13. package/src/isv/deep-links/get-product-link.js +8 -6
  14. package/src/isv/deep-links/get-transaction-link.js +8 -6
  15. package/src/isv/index.js +6 -0
  16. package/src/isv/invitations/index.js +5 -0
  17. package/src/isv/oauth2/get-token-info.js +10 -5
  18. package/src/isv/oauth2/obtain-token.js +9 -4
  19. package/src/isv/oauth2/token-helper.js +28 -26
  20. package/src/isv/poi/cancel-price.js +9 -2
  21. package/src/isv/poi/get-poi.js +4 -3
  22. package/src/isv/poi/get-qrcode.js +40 -9
  23. package/src/isv/poi/get-status.js +196 -168
  24. package/src/isv/poi/list-pois.js +6 -6
  25. package/src/isv/poi/set-price.js +40 -53
  26. package/src/isv/products/audios.js +8 -2
  27. package/src/isv/products/create-product.js +2 -0
  28. package/src/isv/products/delete-product.js +2 -0
  29. package/src/isv/products/get-product-image.js +2 -0
  30. package/src/isv/products/get-product.js +2 -0
  31. package/src/isv/products/list-products.js +2 -0
  32. package/src/isv/products/schedules.js +6 -0
  33. package/src/isv/products/update-product.js +2 -0
  34. package/src/isv/reference/index.js +2 -0
  35. package/src/isv/transactions/cancel-transaction.js +2 -0
  36. package/src/isv/transactions/create-transaction.js +2 -0
  37. package/src/isv/transactions/get-status.js +3 -1
  38. package/src/isv/transactions/process-transaction.js +2 -0
  39. package/src/isv/transactions/simulate-callback.js +21 -10
  40. package/src/isv/transactions/transaction-history.js +2 -0
  41. package/src/merchant/data/export-report.js +2 -0
  42. package/src/merchant/data/generate-report.js +2 -0
  43. package/src/merchant/data/get-export-list.js +2 -0
  44. package/src/merchant/data/get-report-requests.js +2 -0
  45. package/src/merchant/data/get-report-status.js +2 -0
  46. package/src/merchant/data/index.js +15 -10
  47. package/src/merchant/data/list-reports.js +2 -0
  48. package/src/merchant/deep-links/delete-product-link.js +3 -2
  49. package/src/merchant/deep-links/delete-transaction-link.js +3 -2
  50. package/src/merchant/deep-links/get-product-link.js +8 -6
  51. package/src/merchant/deep-links/get-transaction-link.js +8 -6
  52. package/src/merchant/index.js +7 -1
  53. package/src/merchant/logs/index.js +4 -0
  54. package/src/merchant/products/audios.js +15 -14
  55. package/src/merchant/products/create-product.js +3 -2
  56. package/src/merchant/products/delete-product.js +3 -2
  57. package/src/merchant/products/get-product-image.js +3 -2
  58. package/src/merchant/products/get-product.js +3 -2
  59. package/src/merchant/products/list-products.js +3 -2
  60. package/src/merchant/products/schedules.js +11 -10
  61. package/src/merchant/products/update-product.js +3 -2
  62. package/src/merchant/transactions/cancel-transaction.js +3 -2
  63. package/src/merchant/transactions/create-transaction.js +3 -1
  64. package/src/merchant/transactions/get-status.js +4 -3
  65. package/src/merchant/transactions/process-transaction.js +5 -4
  66. package/src/merchant/transactions/simulate-callback.js +325 -313
  67. package/src/merchant/transactions/transaction-history.js +7 -6
  68. package/src/payment-institution/data/index.js +15 -14
  69. package/src/payment-institution/deep-links/delete-transaction-link.js +3 -2
  70. package/src/payment-institution/deep-links/get-transaction-link.js +8 -6
  71. package/src/payment-institution/transactions/create-pi-transaction.js +4 -3
  72. package/src/payment-institution/transactions/finalize-transaction.js +6 -3
  73. package/src/payment-institution/transactions/get-transaction-history.js +8 -5
  74. package/src/payment-institution/transactions/get-transaction-status.js +16 -13
  75. package/src/payment-institution/transactions/process-pi-transaction.js +7 -6
  76. package/src/payment-institution/transactions/simulate-pi-callback.js +388 -375
  77. package/src/payment-institution/transactions/soundbite-transaction.js +6 -5
  78. package/src/proxy-server.js +25 -0
  79. package/src/shared/placements/api.js +51 -0
  80. package/src/shared/placements/tools.js +519 -0
  81. package/src/shared/poi/poi-create-api.js +4 -1
  82. package/src/shared/products/products-api.js +36 -25
  83. package/src/shared/shops/shops-api.js +2 -0
  84. package/src/shared/tool-annotations.js +53 -0
  85. package/src/tools/advanced-code-generator.js +17 -265
  86. package/src/utils/code-examples/common/auth-examples.js +1 -1
  87. package/src/utils/code-examples/isv/products.js +1 -1
  88. package/src/utils/code-examples/isv/transactions.js +1 -1
  89. package/src/utils/code-examples/merchant/transactions.js +1 -1
  90. package/src/utils/format-request.js +2 -0
  91. package/src/utils/json-formatter.js +2 -0
  92. package/src/utils/server-info.js +2 -0
  93. package/src/utils/code-examples.js +0 -646
@@ -1,168 +1,196 @@
1
- import axios from 'axios';
2
- import { createJWTForPartner } from '../../core/auth/jwt-factory.js';
3
- import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
4
- import { apiErrorResult } from '../../shared/api-errors.js';
5
-
6
- /**
7
- * Get POI status via ISV authentication
8
- * @param {Object} params - Parameters for status request
9
- * @returns {Object} POI status response
10
- */
11
- export async function getPOIStatus({ poiId, merchantPartnerId, oauth2Token, useSandbox = true }) {
12
- if (!poiId) {
13
- throw new Error('POI ID is required');
14
- }
15
-
16
- if (!merchantPartnerId) {
17
- throw new Error('Merchant Partner ID is required for ISV operations');
18
- }
19
-
20
- if (!oauth2Token) {
21
- throw new Error('OAuth2 token is required for ISV operations');
22
- }
23
-
24
- const isvPartnerId = getPartnerIdSafe();
25
- const privateKey = getPrivateKeySafe(useSandbox);
26
-
27
- const jwtData = await createJWTForPartner({
28
- partnerId: isvPartnerId,
29
- privateKey,
30
- requestBody: null,
31
- merchantId: merchantPartnerId,
32
- oauth2Token
33
- });
34
-
35
- const headers = {
36
- 'Authorization': `Bearer ${jwtData.token}`,
37
- 'Content-Type': 'application/json',
38
- 'Api-Version': '1'
39
- };
40
-
41
- try {
42
- const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
43
- const response = await axios.get(`${baseUrl}/poi/${poiId}/status`, { headers });
44
-
45
- return {
46
- success: true,
47
- status: response.data,
48
- requestId: response.headers['x-request-id'],
49
- timestamp: new Date().toISOString()
50
- };
51
- } catch (error) {
52
- return apiErrorResult(error);
53
- }
54
- }
55
-
56
- /**
57
- * Get POI status tool implementation for ISV
58
- */
59
- export const getPOIStatusTool = {
60
- name: "payware_poi_get_status",
61
- description: `Get current status of a POI including any pending payment.
62
-
63
- **ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
64
- **Endpoint:** GET /poi/{poiId}/status
65
- **Use Case:** Check if a POI is idle, has a pending price, or is busy with a payment.
66
-
67
- **POI States:**
68
- - IDLE: Ready for new payment
69
- - READY: Price set, waiting for customer scan
70
- - BUSY: Payment in progress
71
- - DISABLED: Not accepting payments
72
-
73
- **Required:** POI ID, Merchant Partner ID, and OAuth2 token.`,
74
-
75
- inputSchema: {
76
- type: "object",
77
- required: ["poiId", "merchantPartnerId", "oauth2Token"],
78
- properties: {
79
- poiId: {
80
- type: "string",
81
- description: "The POI identifier (format: pi + 8 alphanumeric chars, e.g., piABC12345)"
82
- },
83
- merchantPartnerId: {
84
- type: "string",
85
- description: "Partner ID of the target merchant (8 alphanumeric characters)"
86
- },
87
- oauth2Token: {
88
- type: "string",
89
- description: "OAuth2 access token obtained from the merchant"
90
- },
91
- useSandbox: {
92
- type: "boolean",
93
- description: "Use sandbox environment for testing",
94
- default: true
95
- }
96
- }
97
- },
98
-
99
- async handler(args) {
100
- const { poiId, merchantPartnerId, oauth2Token, useSandbox = true } = args;
101
-
102
- if (!poiId) {
103
- throw new Error("POI ID is required");
104
- }
105
-
106
- if (!merchantPartnerId) {
107
- throw new Error("Merchant Partner ID is required");
108
- }
109
-
110
- if (!oauth2Token) {
111
- throw new Error("OAuth2 token is required");
112
- }
113
-
114
- const result = await getPOIStatus({ poiId, merchantPartnerId, oauth2Token, useSandbox });
115
-
116
- if (result.success) {
117
- const status = result.status;
118
- const statusEmoji = {
119
- 'IDLE': '🟢',
120
- 'READY': '🟡',
121
- 'BUSY': '🔴',
122
- 'DISABLED': '⚫'
123
- }[status.status] || '⚪';
124
-
125
- let pendingInfo = '';
126
- if (status.status === 'READY' || status.status === 'BUSY') {
127
- pendingInfo = `
128
- **Pending Payment:**
129
- - Amount: ${status.pendingAmount} ${status.pendingCurrency}
130
- - Expires: ${status.sessionExpiresAt || 'N/A'}${status.transactionId ? `\n- Transaction: ${status.transactionId}` : ''}`;
131
- }
132
-
133
- return {
134
- content: [{
135
- type: "text",
136
- text: `${statusEmoji} **POI Status: ${status.status}**
137
-
138
- **POI ID:** ${status.poiId}
139
- **ISV -> Merchant:** ${getPartnerIdSafe()} -> ${merchantPartnerId}
140
- ${pendingInfo}
141
-
142
- **Status Meaning:**
143
- ${status.status === 'IDLE' ? '✅ POI is ready to accept a new price' : ''}${status.status === 'READY' ? '⏳ Price set, waiting for customer to scan' : ''}${status.status === 'BUSY' ? '🔄 Customer scanned, payment in progress' : ''}${status.status === 'DISABLED' ? '🚫 POI is disabled and cannot accept payments' : ''}
144
-
145
- **Request ID:** ${result.requestId || 'N/A'}
146
- **Timestamp:** ${result.timestamp}`
147
- }]
148
- };
149
- } else {
150
- return {
151
- content: [{
152
- type: "text",
153
- text: `❌ **Failed to Get POI Status**
154
-
155
- **POI ID:** ${poiId}
156
- **Error:** ${result.error.message}
157
- **Status:** ${result.error.status || 'N/A'}
158
-
159
- **Common Issues:**
160
- - INVALID_POI_ID (400): POI ID format invalid (must be pi + 8 alphanumeric chars)
161
- - POI_NOT_FOUND (404): POI doesn't exist
162
-
163
- **Timestamp:** ${result.timestamp}`
164
- }]
165
- };
166
- }
167
- }
168
- };
1
+ import axios from 'axios';
2
+ import { createJWTForPartner } from '../../core/auth/jwt-factory.js';
3
+ import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
4
+ import { apiErrorResult } from '../../shared/api-errors.js';
5
+ import { READS } from '../../shared/tool-annotations.js';
6
+
7
+ /**
8
+ * Get POI status via ISV authentication
9
+ * @param {Object} params - Parameters for status request
10
+ * @returns {Object} POI status response
11
+ */
12
+ export async function getPOIStatus({ poiId, sessionToken, merchantPartnerId, oauth2Token, useSandbox = true }) {
13
+ if (!poiId) {
14
+ throw new Error('POI ID is required');
15
+ }
16
+
17
+ if (!merchantPartnerId) {
18
+ throw new Error('Merchant Partner ID is required for ISV operations');
19
+ }
20
+
21
+ if (!oauth2Token) {
22
+ throw new Error('OAuth2 token is required for ISV operations');
23
+ }
24
+
25
+ const isvPartnerId = getPartnerIdSafe();
26
+ const privateKey = getPrivateKeySafe(useSandbox);
27
+
28
+ const jwtData = await createJWTForPartner({
29
+ partnerId: isvPartnerId,
30
+ privateKey,
31
+ requestBody: null,
32
+ merchantId: merchantPartnerId,
33
+ oauth2Token
34
+ });
35
+
36
+ const headers = {
37
+ 'Authorization': `Bearer ${jwtData.token}`,
38
+ 'Content-Type': 'application/json',
39
+ 'Api-Version': '1'
40
+ };
41
+
42
+ try {
43
+ const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
44
+ // The session token pins one sale: with it the response also carries that session's
45
+ // transactionId and transactionStatus. A GET is signed without a body, so the query string is free.
46
+ const query = sessionToken ? `?sessionToken=${encodeURIComponent(sessionToken)}` : '';
47
+ const response = await axios.get(`${baseUrl}/poi/${poiId}/status${query}`, { headers });
48
+
49
+ return {
50
+ success: true,
51
+ status: response.data,
52
+ requestId: response.headers['x-request-id'],
53
+ timestamp: new Date().toISOString()
54
+ };
55
+ } catch (error) {
56
+ return apiErrorResult(error);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Get POI status tool implementation for ISV
62
+ */
63
+ export const getPOIStatusTool = {
64
+ name: "payware_poi_get_status",
65
+ annotations: READS,
66
+ description: `Get the current status of a POI and the outcome of its sales.
67
+
68
+ **ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
69
+ **Endpoint:** GET /poi/{poiId}/status[?sessionToken=...]
70
+ **Use Case:** Follow a sale to its outcome - the backup to callbacks, and on Basic (no transaction
71
+ endpoints) the only way when a callback is lost or there is no backend.
72
+
73
+ **POI States:**
74
+ - IDLE: no pending price and no sale running
75
+ - READY: price set, waiting for a scan (wins over BUSY: the POI can take the next price while a sale runs)
76
+ - BUSY: a customer scanned and the sale is running
77
+
78
+ **Every response** carries lastTransactionId / lastTransactionStatus - the POI's most recent sale, running
79
+ (ACTIVE) or finished (CONFIRMED, DECLINED, FAILED, CANCELLED, EXPIRED). Once the POI is back to IDLE,
80
+ lastTransactionStatus is how the last sale ended.
81
+
82
+ **With sessionToken** (from payware_poi_set_price) the response also carries transactionId /
83
+ transactionStatus for exactly that sale - use it when the POI may be re-armed before the previous sale
84
+ finishes. Poll every 2-3 seconds and stop at a final status; the read is rate limited.
85
+
86
+ **Required:** POI ID, Merchant Partner ID, and OAuth2 token.`,
87
+
88
+ inputSchema: {
89
+ type: "object",
90
+ required: ["poiId", "merchantPartnerId", "oauth2Token"],
91
+ properties: {
92
+ poiId: {
93
+ type: "string",
94
+ description: "The POI identifier (format: pi + 8 alphanumeric chars, e.g., piABC12345)"
95
+ },
96
+ sessionToken: {
97
+ type: "string",
98
+ description: "Optional. The sessionToken returned by payware_poi_set_price; returns that sale's transaction and status"
99
+ },
100
+ merchantPartnerId: {
101
+ type: "string",
102
+ description: "Partner ID of the target merchant (8 alphanumeric characters)"
103
+ },
104
+ oauth2Token: {
105
+ type: "string",
106
+ description: "OAuth2 access token obtained from the merchant"
107
+ },
108
+ useSandbox: {
109
+ type: "boolean",
110
+ description: "Use sandbox environment for testing",
111
+ default: true
112
+ }
113
+ }
114
+ },
115
+
116
+ async handler(args) {
117
+ const { poiId, sessionToken, merchantPartnerId, oauth2Token, useSandbox = true } = args;
118
+
119
+ if (!poiId) {
120
+ throw new Error("POI ID is required");
121
+ }
122
+
123
+ if (!merchantPartnerId) {
124
+ throw new Error("Merchant Partner ID is required");
125
+ }
126
+
127
+ if (!oauth2Token) {
128
+ throw new Error("OAuth2 token is required");
129
+ }
130
+
131
+ const result = await getPOIStatus({ poiId, sessionToken, merchantPartnerId, oauth2Token, useSandbox });
132
+
133
+ if (result.success) {
134
+ const status = result.status;
135
+ const statusEmoji = {
136
+ 'IDLE': '🟢',
137
+ 'READY': '🟡',
138
+ 'BUSY': '🔴'
139
+ }[status.status] || '⚪';
140
+ const meaning = {
141
+ 'IDLE': status.lastTransactionId
142
+ ? `No sale running. The last sale ended ${status.lastTransactionStatus}.`
143
+ : 'No sale running, and the POI has not had a sale yet.',
144
+ 'READY': 'Price set, waiting for a customer to scan.',
145
+ 'BUSY': 'A customer scanned and the sale is running.'
146
+ }[status.status] || '';
147
+
148
+ const pendingInfo = status.status === 'READY' ? `
149
+ **Pending Price:**
150
+ - Amount: ${status.pendingAmount} ${status.pendingCurrency}
151
+ - Session expires: ${status.sessionExpiresAt || 'N/A'}` : '';
152
+
153
+ const lastSale = status.lastTransactionId ? `
154
+ **Last Sale:** ${status.lastTransactionId} - ${status.lastTransactionStatus}` : '';
155
+
156
+ const pinnedSale = sessionToken ? `
157
+ **Sale for this session token:** ${status.transactionId
158
+ ? `${status.transactionId} - ${status.transactionStatus}`
159
+ : 'not scanned yet (or the token does not belong to this POI)'}` : '';
160
+
161
+ return {
162
+ content: [{
163
+ type: "text",
164
+ text: `${statusEmoji} **POI Status: ${status.status}**
165
+
166
+ **POI ID:** ${status.poiId}
167
+ **ISV -> Merchant:** ${getPartnerIdSafe()} -> ${merchantPartnerId}
168
+ ${pendingInfo}${lastSale}${pinnedSale}
169
+
170
+ **Meaning:** ${meaning}
171
+
172
+ **Request ID:** ${result.requestId || 'N/A'}
173
+ **Timestamp:** ${result.timestamp}`
174
+ }]
175
+ };
176
+ } else {
177
+ return {
178
+ content: [{
179
+ type: "text",
180
+ text: `❌ **Failed to Get POI Status**
181
+
182
+ **POI ID:** ${poiId}
183
+ **Error:** ${result.error.message}
184
+ **Status:** ${result.error.status || 'N/A'}
185
+
186
+ **Common Issues:**
187
+ - ERR_INVALID_POI_ID (400): POI ID format invalid (must be pi + 8 alphanumeric chars)
188
+ - ERR_POI_NOT_FOUND (404): POI doesn't exist, or is disabled
189
+ - ERR_SHOP_NOT_IN_SCOPE (403): the POI's shop is not assigned to you
190
+
191
+ **Timestamp:** ${result.timestamp}`
192
+ }]
193
+ };
194
+ }
195
+ }
196
+ };
@@ -2,6 +2,7 @@ import axios from 'axios';
2
2
  import { createJWTForPartner } from '../../core/auth/jwt-factory.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
  * List all POIs for a merchant via ISV authentication
@@ -58,13 +59,14 @@ export async function listPOIs({ merchantPartnerId, oauth2Token, useSandbox = tr
58
59
  */
59
60
  export const listPOIsTool = {
60
61
  name: "payware_poi_list",
62
+ annotations: READS,
61
63
  description: `List all POIs (Points of Interaction) for a merchant.
62
64
 
63
65
  **ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
64
66
  **Endpoint:** GET /poi
65
67
  **Use Case:** Retrieve all physical payment points configured for a merchant.
66
68
 
67
- POI states: IDLE (ready), READY (price set), BUSY (payment in progress), DISABLED.
69
+ POI states: IDLE (no price, no sale running), READY (price set, waiting for a scan), BUSY (scanned, sale running). A disabled POI is not a state: it shows isActive false.
68
70
 
69
71
  **Required:** Merchant Partner ID and OAuth2 token for ISV authentication.`,
70
72
 
@@ -106,13 +108,12 @@ POI states: IDLE (ready), READY (price set), BUSY (payment in progress), DISABLE
106
108
  const statusEmoji = {
107
109
  'IDLE': '🟢',
108
110
  'READY': '🟡',
109
- 'BUSY': '🔴',
110
- 'DISABLED': '⚫'
111
+ 'BUSY': '🔴'
111
112
  }[poi.status] || '⚪';
112
113
 
113
114
  return `${statusEmoji} **${poi.name}** (${poi.poiId})
114
115
  Shop: ${poi.shopName || poi.shopCode}
115
- Status: ${poi.status}${poi.pendingAmount && poi.pendingAmount !== '0.00' ? ` | Pending: ${poi.pendingAmount} ${poi.pendingCurrency}` : ''}`;
116
+ Status: ${poi.status}${poi.isActive === false ? ' (disabled)' : ''}${poi.pendingAmount && poi.pendingAmount !== '0.00' ? ` | Pending: ${poi.pendingAmount} ${poi.pendingCurrency}` : ''}`;
116
117
  }).join('\n\n');
117
118
 
118
119
  return {
@@ -127,8 +128,7 @@ ${poisList || 'No POIs found for this merchant.'}
127
128
  **Status Legend:**
128
129
  🟢 IDLE - Ready for new payment
129
130
  🟡 READY - Price set, waiting for customer
130
- 🔴 BUSY - Payment in progress
131
- ⚫ DISABLED - Not accepting payments
131
+ 🔴 BUSY - Scanned, sale running (payware_poi_get_status shows its outcome)
132
132
 
133
133
  **Request ID:** ${result.requestId || 'N/A'}
134
134
  **Timestamp:** ${result.timestamp}`
@@ -2,13 +2,14 @@ import axios from 'axios';
2
2
  import { createJWTForPartner } from '../../core/auth/jwt-factory.js';
3
3
  import { getSandboxUrl, getProductionUrl, getPartnerIdSafe, getPrivateKeySafe } from '../../config/env.js';
4
4
  import { apiErrorResult } from '../../shared/api-errors.js';
5
+ import { CHANGES } from '../../shared/tool-annotations.js';
5
6
 
6
7
  /**
7
8
  * Set price on a POI via ISV authentication
8
9
  * @param {Object} params - Parameters for set price request
9
10
  * @returns {Object} Set price response
10
11
  */
11
- export async function setPOIPrice({ poiId, amount, currency, reasonL1, reasonL2, timeToLive, callbackUrl, passbackParams, merchantPartnerId, oauth2Token, useSandbox = true }) {
12
+ export async function setPOIPrice({ poiId, amount, currency, ttlSeconds, merchantPartnerId, oauth2Token, useSandbox = true }) {
12
13
  if (!poiId) {
13
14
  throw new Error('POI ID is required');
14
15
  }
@@ -21,10 +22,6 @@ export async function setPOIPrice({ poiId, amount, currency, reasonL1, reasonL2,
21
22
  throw new Error('Currency is required');
22
23
  }
23
24
 
24
- if (!reasonL1) {
25
- throw new Error('Reason (reasonL1) is required');
26
- }
27
-
28
25
  if (!merchantPartnerId) {
29
26
  throw new Error('Merchant Partner ID is required for ISV operations');
30
27
  }
@@ -36,17 +33,16 @@ export async function setPOIPrice({ poiId, amount, currency, reasonL1, reasonL2,
36
33
  const isvPartnerId = getPartnerIdSafe();
37
34
  const privateKey = getPrivateKeySafe(useSandbox);
38
35
 
39
- // Build request body
36
+ // amount, currency and ttlSeconds are the only fields the server reads (POIPriceRequest); anything
37
+ // else is ignored without an error. This used to send reasonL1 (and demand it), reasonL2,
38
+ // timeToLive, callbackUrl and passbackParams - none of which the server has - so a caller relying
39
+ // on the per-price callbackUrl got no callbacks at all. The callback URL belongs to the POI.
40
40
  const requestBody = {
41
41
  amount: String(amount),
42
- currency: currency.toUpperCase(),
43
- reasonL1
42
+ currency: currency.toUpperCase()
44
43
  };
45
44
 
46
- if (reasonL2) requestBody.reasonL2 = reasonL2;
47
- if (timeToLive) requestBody.timeToLive = timeToLive;
48
- if (callbackUrl) requestBody.callbackUrl = callbackUrl;
49
- if (passbackParams) requestBody.passbackParams = passbackParams;
45
+ if (ttlSeconds) requestBody.ttlSeconds = ttlSeconds;
50
46
 
51
47
  const jwtData = await createJWTForPartner({
52
48
  partnerId: isvPartnerId,
@@ -82,19 +78,28 @@ export async function setPOIPrice({ poiId, amount, currency, reasonL1, reasonL2,
82
78
  */
83
79
  export const setPOIPriceTool = {
84
80
  name: "payware_poi_set_price",
81
+ annotations: CHANGES,
85
82
  description: `Set a pending price on a POI for customer payment.
86
83
 
87
84
  **ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
88
85
  **Endpoint:** PUT /poi/{poiId}/price
89
86
  **Use Case:** Set the amount a customer should pay when they scan the POI.
90
87
 
91
- The POI will transition to READY state and wait for a customer scan until the TTL expires.
88
+ The POI moves to READY and waits for a scan until the TTL expires. The scan creates the transaction and
89
+ the POI is BUSY until the sale is final.
90
+
91
+ **Only amount, currency and ttlSeconds are accepted.** There is no description, callback URL or passback
92
+ field on a price - the callback URL is set on the POI when it is created, and a POI without one sends no
93
+ callbacks.
94
+
95
+ **Keep the returned sessionToken** until the sale has a final status: pass it to
96
+ payware_poi_get_status to read that sale's transaction and outcome.
92
97
 
93
- **Required:** POI ID, amount, currency, reason, Merchant Partner ID, and OAuth2 token.`,
98
+ **Required:** POI ID, amount, currency, Merchant Partner ID, and OAuth2 token.`,
94
99
 
95
100
  inputSchema: {
96
101
  type: "object",
97
- required: ["poiId", "amount", "currency", "reasonL1", "merchantPartnerId", "oauth2Token"],
102
+ required: ["poiId", "amount", "currency", "merchantPartnerId", "oauth2Token"],
98
103
  properties: {
99
104
  poiId: {
100
105
  type: "string",
@@ -102,31 +107,15 @@ The POI will transition to READY state and wait for a customer scan until the TT
102
107
  },
103
108
  amount: {
104
109
  type: "string",
105
- description: "Payment amount (e.g., '25.50')"
110
+ description: "Payment amount (e.g., '25.50'), positive and no finer than the currency's smallest unit"
106
111
  },
107
112
  currency: {
108
113
  type: "string",
109
114
  description: "ISO 4217 currency code (e.g., 'EUR', 'USD', 'GBP')"
110
115
  },
111
- reasonL1: {
112
- type: "string",
113
- description: "Payment description (e.g., 'Table 5', 'Order #123')"
114
- },
115
- reasonL2: {
116
- type: "string",
117
- description: "Additional description (optional)"
118
- },
119
- timeToLive: {
116
+ ttlSeconds: {
120
117
  type: "integer",
121
- description: "Seconds until price expires (60-600, default: 120)"
122
- },
123
- callbackUrl: {
124
- type: "string",
125
- description: "Override callback URL for this payment (optional)"
126
- },
127
- passbackParams: {
128
- type: "string",
129
- description: "Data to pass back in callback (optional, max 200 chars)"
118
+ description: "Seconds until the price expires if nobody scans it (60-600). Defaults to the POI's own ttlSeconds."
130
119
  },
131
120
  merchantPartnerId: {
132
121
  type: "string",
@@ -145,18 +134,16 @@ The POI will transition to READY state and wait for a customer scan until the TT
145
134
  },
146
135
 
147
136
  async handler(args) {
148
- const { poiId, amount, currency, reasonL1, reasonL2, timeToLive, callbackUrl, passbackParams, merchantPartnerId, oauth2Token, useSandbox = true } = args;
137
+ const { poiId, amount, currency, ttlSeconds, merchantPartnerId, oauth2Token, useSandbox = true } = args;
149
138
 
150
139
  if (!poiId) throw new Error("POI ID is required");
151
140
  if (!amount) throw new Error("Amount is required");
152
141
  if (!currency) throw new Error("Currency is required");
153
- if (!reasonL1) throw new Error("Reason (reasonL1) is required");
154
142
  if (!merchantPartnerId) throw new Error("Merchant Partner ID is required");
155
143
  if (!oauth2Token) throw new Error("OAuth2 token is required");
156
144
 
157
145
  const result = await setPOIPrice({
158
- poiId, amount, currency, reasonL1, reasonL2, timeToLive, callbackUrl, passbackParams,
159
- merchantPartnerId, oauth2Token, useSandbox
146
+ poiId, amount, currency, ttlSeconds, merchantPartnerId, oauth2Token, useSandbox
160
147
  });
161
148
 
162
149
  if (result.success) {
@@ -169,19 +156,17 @@ The POI will transition to READY state and wait for a customer scan until the TT
169
156
 
170
157
  **POI ID:** ${data.poiId || poiId}
171
158
  **Status:** 🟡 ${data.status || 'READY'}
172
-
173
- **Payment Details:**
174
- - Amount: **${data.amount || amount} ${data.currency || currency}**
175
- - Reason: ${reasonL1}${reasonL2 ? ` - ${reasonL2}` : ''}
159
+ **Amount:** ${amount} ${currency.toUpperCase()}
176
160
 
177
161
  **Session:**
178
- - Token: ${data.sessionToken || 'N/A'}
179
- - Expires: ${data.sessionExpiresAt || 'N/A'}
162
+ - Token: ${data.sessionToken || 'N/A'} - keep it until the sale is final
163
+ - Price expires if not scanned: ${data.expiresAt || 'N/A'}
180
164
 
181
165
  **Next Steps:**
182
- 1. Customer scans the POI (QR code, NFC tag, or barcode)
183
- 2. Customer sees the amount and confirms payment
184
- 3. You receive a callback when payment completes
166
+ 1. The customer scans the POI (QR code, NFC tag or BLE); the transaction is created and the POI is BUSY
167
+ 2. Outcome by callback, if the POI has a callbackUrl: poi.scanned with the transaction id, then TRANSACTION_FINALIZED with the final status (both carry the poiId)
168
+ 3. Or poll payware_poi_get_status every 2-3 seconds with this sessionToken until transactionStatus is final
169
+ 4. To cancel, use payware_poi_cancel_price - possible only before the scan
185
170
 
186
171
  **ISV -> Merchant:** ${getPartnerIdSafe()} -> ${merchantPartnerId}
187
172
  **Request ID:** ${result.requestId || 'N/A'}
@@ -202,12 +187,14 @@ The POI will transition to READY state and wait for a customer scan until the TT
202
187
  **Status:** ${result.error.status || 'N/A'}
203
188
 
204
189
  **Common Issues:**
205
- - INVALID_POI_ID (400): POI ID format invalid (must be pi + 8 alphanumeric chars)
206
- - POI_NOT_FOUND (404): POI doesn't exist or doesn't belong to merchant
207
- - POI_DISABLED (409): POI is disabled
208
- - POI_NOT_IDLE (409): POI already has a pending price (cancel it first)
209
- - INVALID_AMOUNT (400): Check amount format (e.g., '25.50')
210
- - UNKNOWN_CURRENCY (400): Invalid currency code
190
+ - ERR_INVALID_POI_ID (400): POI ID format invalid (must be pi + 8 alphanumeric chars)
191
+ - ERR_POI_NOT_FOUND (404): POI doesn't exist or doesn't belong to the merchant
192
+ - ERR_SHOP_NOT_IN_SCOPE (403): the POI's shop is not assigned to you
193
+ - ERR_POI_DISABLED (409): POI is disabled
194
+ - ERR_POI_ALREADY_HAS_PENDING_PRICE (409): POI already has a pending price (cancel it first)
195
+ - ERR_INVALID_AMOUNT (400): amount finer than the currency's smallest unit
196
+ - ERR_MISSING_CURRENCY (400): currency missing or unknown
197
+ - ERR_VALIDATION_FAILED (400): amount missing or not positive, or ttlSeconds outside 60-600
211
198
 
212
199
  **Timestamp:** ${result.timestamp}`
213
200
  }]