@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.
- package/.env.example +4 -0
- package/README.md +18 -0
- package/package.json +7 -9
- package/src/auth/rsa-keys.js +12 -6
- package/src/auth/sandbox-setup.js +2 -0
- package/src/config/toolsets.js +104 -0
- package/src/core/auth/jwt-factory.js +4 -1
- package/src/core/auth/jwt-token.js +4 -0
- package/src/index.js +10 -6
- package/src/isv/data/index.js +15 -14
- package/src/isv/deep-links/delete-product-link.js +3 -2
- package/src/isv/deep-links/delete-transaction-link.js +3 -2
- package/src/isv/deep-links/get-product-link.js +8 -6
- package/src/isv/deep-links/get-transaction-link.js +8 -6
- package/src/isv/index.js +6 -0
- package/src/isv/invitations/index.js +5 -0
- package/src/isv/oauth2/get-token-info.js +10 -5
- package/src/isv/oauth2/obtain-token.js +9 -4
- package/src/isv/oauth2/token-helper.js +28 -26
- package/src/isv/poi/cancel-price.js +9 -2
- package/src/isv/poi/get-poi.js +4 -3
- package/src/isv/poi/get-qrcode.js +40 -9
- package/src/isv/poi/get-status.js +196 -168
- package/src/isv/poi/list-pois.js +6 -6
- package/src/isv/poi/set-price.js +40 -53
- package/src/isv/products/audios.js +8 -2
- package/src/isv/products/create-product.js +2 -0
- package/src/isv/products/delete-product.js +2 -0
- package/src/isv/products/get-product-image.js +2 -0
- package/src/isv/products/get-product.js +2 -0
- package/src/isv/products/list-products.js +2 -0
- package/src/isv/products/schedules.js +6 -0
- package/src/isv/products/update-product.js +2 -0
- package/src/isv/reference/index.js +2 -0
- package/src/isv/transactions/cancel-transaction.js +2 -0
- package/src/isv/transactions/create-transaction.js +2 -0
- package/src/isv/transactions/get-status.js +3 -1
- package/src/isv/transactions/process-transaction.js +2 -0
- package/src/isv/transactions/simulate-callback.js +21 -10
- package/src/isv/transactions/transaction-history.js +2 -0
- package/src/merchant/data/export-report.js +2 -0
- package/src/merchant/data/generate-report.js +2 -0
- package/src/merchant/data/get-export-list.js +2 -0
- package/src/merchant/data/get-report-requests.js +2 -0
- package/src/merchant/data/get-report-status.js +2 -0
- package/src/merchant/data/index.js +15 -10
- package/src/merchant/data/list-reports.js +2 -0
- package/src/merchant/deep-links/delete-product-link.js +3 -2
- package/src/merchant/deep-links/delete-transaction-link.js +3 -2
- package/src/merchant/deep-links/get-product-link.js +8 -6
- package/src/merchant/deep-links/get-transaction-link.js +8 -6
- package/src/merchant/index.js +7 -1
- package/src/merchant/logs/index.js +4 -0
- package/src/merchant/products/audios.js +15 -14
- package/src/merchant/products/create-product.js +3 -2
- package/src/merchant/products/delete-product.js +3 -2
- package/src/merchant/products/get-product-image.js +3 -2
- package/src/merchant/products/get-product.js +3 -2
- package/src/merchant/products/list-products.js +3 -2
- package/src/merchant/products/schedules.js +11 -10
- package/src/merchant/products/update-product.js +3 -2
- package/src/merchant/transactions/cancel-transaction.js +3 -2
- package/src/merchant/transactions/create-transaction.js +3 -1
- package/src/merchant/transactions/get-status.js +4 -3
- package/src/merchant/transactions/process-transaction.js +5 -4
- package/src/merchant/transactions/simulate-callback.js +325 -313
- package/src/merchant/transactions/transaction-history.js +7 -6
- package/src/payment-institution/data/index.js +15 -14
- package/src/payment-institution/deep-links/delete-transaction-link.js +3 -2
- package/src/payment-institution/deep-links/get-transaction-link.js +8 -6
- package/src/payment-institution/transactions/create-pi-transaction.js +4 -3
- package/src/payment-institution/transactions/finalize-transaction.js +6 -3
- package/src/payment-institution/transactions/get-transaction-history.js +8 -5
- package/src/payment-institution/transactions/get-transaction-status.js +16 -13
- package/src/payment-institution/transactions/process-pi-transaction.js +7 -6
- package/src/payment-institution/transactions/simulate-pi-callback.js +388 -375
- package/src/payment-institution/transactions/soundbite-transaction.js +6 -5
- package/src/proxy-server.js +25 -0
- package/src/shared/placements/api.js +51 -0
- package/src/shared/placements/tools.js +519 -0
- package/src/shared/poi/poi-create-api.js +4 -1
- package/src/shared/products/products-api.js +36 -25
- package/src/shared/shops/shops-api.js +2 -0
- package/src/shared/tool-annotations.js +53 -0
- package/src/tools/advanced-code-generator.js +17 -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/format-request.js +2 -0
- package/src/utils/json-formatter.js +2 -0
- package/src/utils/server-info.js +2 -0
- package/src/utils/code-examples.js +0 -646
|
@@ -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
|
-
|
|
8
|
-
*
|
|
9
|
-
* @
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
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
|
+
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
|
+
};
|
package/src/isv/poi/list-pois.js
CHANGED
|
@@ -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 (
|
|
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 -
|
|
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}`
|
package/src/isv/poi/set-price.js
CHANGED
|
@@ -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,
|
|
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
|
-
//
|
|
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 (
|
|
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
|
|
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,
|
|
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", "
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
-
|
|
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.
|
|
183
|
-
2.
|
|
184
|
-
3.
|
|
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
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
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
|
}]
|