@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.
- package/package.json +1 -1
- package/src/auth/sandbox-setup.js +1 -1
- package/src/core/auth/jwt-token.js +61 -51
- package/src/isv/invitations/index.js +2 -1
- package/src/isv/oauth2/get-token-info.js +7 -5
- package/src/isv/oauth2/obtain-token.js +6 -4
- package/src/isv/oauth2/token-helper.js +26 -26
- package/src/isv/poi/cancel-price.js +7 -2
- package/src/isv/poi/get-poi.js +2 -3
- package/src/isv/poi/get-status.js +194 -168
- package/src/isv/poi/list-pois.js +4 -6
- package/src/isv/poi/set-price.js +38 -53
- package/src/isv/products/create-product.js +1 -1
- package/src/isv/transactions/simulate-callback.js +19 -10
- package/src/merchant/transactions/create-transaction.js +1 -1
- package/src/merchant/transactions/simulate-callback.js +322 -313
- package/src/merchant/transactions/transaction-history.js +2 -2
- package/src/payment-institution/transactions/get-transaction-history.js +1 -1
- package/src/payment-institution/transactions/get-transaction-status.js +1 -1
- package/src/payment-institution/transactions/simulate-pi-callback.js +385 -375
- package/src/payment-institution/transactions/soundbite-transaction.js +1 -1
- package/src/shared/poi/poi-create-api.js +1 -1
- package/src/tools/advanced-code-generator.js +14 -265
- package/src/utils/code-examples/common/auth-examples.js +1 -1
- package/src/utils/code-examples/isv/products.js +1 -1
- package/src/utils/code-examples/isv/transactions.js +1 -1
- package/src/utils/code-examples/merchant/transactions.js +1 -1
- 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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
**
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
${
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
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
|
+
};
|
package/src/isv/poi/list-pois.js
CHANGED
|
@@ -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 (
|
|
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 -
|
|
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}`
|
package/src/isv/poi/set-price.js
CHANGED
|
@@ -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,
|
|
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
|
-
//
|
|
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 (
|
|
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
|
|
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,
|
|
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", "
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
-
|
|
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.
|
|
183
|
-
2.
|
|
184
|
-
3.
|
|
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
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
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
|
|
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
|