@payware/mcp-server 1.3.1 → 1.3.3

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