@payware/mcp-server 1.0.0 → 1.1.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/package.json +1 -1
- package/src/isv/data/index.js +23 -0
- package/src/isv/deep-links/delete-product-link.js +1 -1
- package/src/isv/deep-links/delete-transaction-link.js +1 -1
- package/src/isv/deep-links/get-product-link.js +1 -1
- package/src/isv/deep-links/get-transaction-link.js +1 -1
- package/src/isv/index.js +11 -1
- package/src/isv/poi/cancel-price.js +149 -0
- package/src/isv/poi/get-poi.js +190 -0
- package/src/isv/poi/get-qrcode.js +151 -0
- package/src/isv/poi/get-status.js +176 -0
- package/src/isv/poi/index.js +32 -0
- package/src/isv/poi/list-pois.js +168 -0
- package/src/isv/poi/set-price.js +225 -0
- package/src/isv/transactions/get-status.js +1 -1
- package/src/isv/transactions/transaction-history.js +2 -1
- package/src/mcp-proxy.js +2 -2
- package/src/merchant/data/export-report.js +3 -4
- package/src/merchant/data/generate-report.js +3 -4
- package/src/merchant/data/get-export-list.js +140 -0
- package/src/merchant/data/get-report-requests.js +125 -0
- package/src/merchant/data/get-report-status.js +3 -4
- package/src/merchant/data/index.js +64 -25
- package/src/merchant/data/list-reports.js +107 -0
- package/src/merchant/deep-links/delete-product-link.js +1 -1
- package/src/merchant/deep-links/delete-transaction-link.js +1 -1
- package/src/merchant/deep-links/get-product-link.js +1 -1
- package/src/merchant/deep-links/get-transaction-link.js +1 -1
- package/src/merchant/transactions/create-transaction.js +4 -2
- package/src/merchant/transactions/get-status.js +1 -1
- package/src/merchant/transactions/process-transaction.js +10 -1
- package/src/merchant/transactions/simulate-callback.js +11 -2
- package/src/merchant/transactions/transaction-history.js +1 -1
- package/src/payment-institution/data/index.js +23 -0
- package/src/payment-institution/deep-links/delete-transaction-link.js +1 -1
- package/src/payment-institution/deep-links/get-transaction-link.js +1 -1
- package/src/payment-institution/transactions/create-pi-transaction.js +2 -0
- package/src/payment-institution/transactions/finalize-transaction.js +1 -0
- 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/process-pi-transaction.js +12 -0
- package/src/payment-institution/transactions/simulate-pi-callback.js +12 -2
- package/src/payment-institution/transactions/soundbite-transaction.js +2 -0
- package/src/shared/data/data-api.js +97 -0
- package/src/utils/code-examples/merchant/transactions.js +2 -1
- package/src/utils/code-examples/payment-institution/transactions.js +1 -0
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get POI status via ISV authentication
|
|
7
|
+
* @param {Object} params - Parameters for status request
|
|
8
|
+
* @returns {Object} POI status response
|
|
9
|
+
*/
|
|
10
|
+
export async function getPOIStatus({ poiId, merchantPartnerId, oauth2Token, useSandbox = true }) {
|
|
11
|
+
if (!poiId) {
|
|
12
|
+
throw new Error('POI ID is required');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!merchantPartnerId) {
|
|
16
|
+
throw new Error('Merchant Partner ID is required for ISV operations');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!oauth2Token) {
|
|
20
|
+
throw new Error('OAuth2 token is required for ISV operations');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isvPartnerId = getPartnerIdSafe();
|
|
24
|
+
const privateKey = getPrivateKeySafe(useSandbox);
|
|
25
|
+
|
|
26
|
+
const jwtData = await createJWTForPartner({
|
|
27
|
+
partnerId: isvPartnerId,
|
|
28
|
+
privateKey,
|
|
29
|
+
requestBody: null,
|
|
30
|
+
merchantId: merchantPartnerId,
|
|
31
|
+
oauth2Token
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const headers = {
|
|
35
|
+
'Authorization': `Bearer ${jwtData.token}`,
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'Api-Version': '1'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
|
|
42
|
+
const response = await axios.get(`${baseUrl}/poi/${poiId}/status`, { headers });
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
status: response.data,
|
|
47
|
+
requestId: response.headers['x-request-id'],
|
|
48
|
+
timestamp: new Date().toISOString()
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: {
|
|
54
|
+
message: error.response?.data?.message || error.message,
|
|
55
|
+
status: error.response?.status,
|
|
56
|
+
code: error.response?.data?.code,
|
|
57
|
+
details: error.response?.data
|
|
58
|
+
},
|
|
59
|
+
timestamp: new Date().toISOString()
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get POI status tool implementation for ISV
|
|
66
|
+
*/
|
|
67
|
+
export const getPOIStatusTool = {
|
|
68
|
+
name: "payware_poi_get_status",
|
|
69
|
+
description: `Get current status of a POI including any pending payment.
|
|
70
|
+
|
|
71
|
+
**ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
|
|
72
|
+
**Endpoint:** GET /poi/{poiId}/status
|
|
73
|
+
**Use Case:** Check if a POI is idle, has a pending price, or is busy with a payment.
|
|
74
|
+
|
|
75
|
+
**POI States:**
|
|
76
|
+
- IDLE: Ready for new payment
|
|
77
|
+
- READY: Price set, waiting for customer scan
|
|
78
|
+
- BUSY: Payment in progress
|
|
79
|
+
- DISABLED: Not accepting payments
|
|
80
|
+
|
|
81
|
+
**Required:** POI ID, Merchant Partner ID, and OAuth2 token.`,
|
|
82
|
+
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
required: ["poiId", "merchantPartnerId", "oauth2Token"],
|
|
86
|
+
properties: {
|
|
87
|
+
poiId: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: "The POI identifier (format: pi + 8 alphanumeric chars, e.g., piABC12345)"
|
|
90
|
+
},
|
|
91
|
+
merchantPartnerId: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Partner ID of the target merchant (8 alphanumeric characters)"
|
|
94
|
+
},
|
|
95
|
+
oauth2Token: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "OAuth2 access token obtained from the merchant"
|
|
98
|
+
},
|
|
99
|
+
useSandbox: {
|
|
100
|
+
type: "boolean",
|
|
101
|
+
description: "Use sandbox environment for testing",
|
|
102
|
+
default: true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async handler(args) {
|
|
108
|
+
const { poiId, merchantPartnerId, oauth2Token, useSandbox = true } = args;
|
|
109
|
+
|
|
110
|
+
if (!poiId) {
|
|
111
|
+
throw new Error("POI ID is required");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!merchantPartnerId) {
|
|
115
|
+
throw new Error("Merchant Partner ID is required");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!oauth2Token) {
|
|
119
|
+
throw new Error("OAuth2 token is required");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const result = await getPOIStatus({ poiId, merchantPartnerId, oauth2Token, useSandbox });
|
|
123
|
+
|
|
124
|
+
if (result.success) {
|
|
125
|
+
const status = result.status;
|
|
126
|
+
const statusEmoji = {
|
|
127
|
+
'IDLE': '🟢',
|
|
128
|
+
'READY': '🟡',
|
|
129
|
+
'BUSY': '🔴',
|
|
130
|
+
'DISABLED': '⚫'
|
|
131
|
+
}[status.status] || '⚪';
|
|
132
|
+
|
|
133
|
+
let pendingInfo = '';
|
|
134
|
+
if (status.status === 'READY' || status.status === 'BUSY') {
|
|
135
|
+
pendingInfo = `
|
|
136
|
+
**Pending Payment:**
|
|
137
|
+
- Amount: ${status.pendingAmount} ${status.pendingCurrency}
|
|
138
|
+
- Expires: ${status.sessionExpiresAt || 'N/A'}${status.transactionId ? `\n- Transaction: ${status.transactionId}` : ''}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
content: [{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: `${statusEmoji} **POI Status: ${status.status}**
|
|
145
|
+
|
|
146
|
+
**POI ID:** ${status.poiId}
|
|
147
|
+
**ISV -> Merchant:** ${getPartnerIdSafe()} -> ${merchantPartnerId}
|
|
148
|
+
${pendingInfo}
|
|
149
|
+
|
|
150
|
+
**Status Meaning:**
|
|
151
|
+
${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' : ''}
|
|
152
|
+
|
|
153
|
+
**Request ID:** ${result.requestId || 'N/A'}
|
|
154
|
+
**Timestamp:** ${result.timestamp}`
|
|
155
|
+
}]
|
|
156
|
+
};
|
|
157
|
+
} else {
|
|
158
|
+
return {
|
|
159
|
+
content: [{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: `❌ **Failed to Get POI Status**
|
|
162
|
+
|
|
163
|
+
**POI ID:** ${poiId}
|
|
164
|
+
**Error:** ${result.error.message}
|
|
165
|
+
**Status:** ${result.error.status || 'N/A'}
|
|
166
|
+
|
|
167
|
+
**Common Issues:**
|
|
168
|
+
- INVALID_POI_ID (400): POI ID format invalid (must be pi + 8 alphanumeric chars)
|
|
169
|
+
- POI_NOT_FOUND (404): POI doesn't exist
|
|
170
|
+
|
|
171
|
+
**Timestamp:** ${result.timestamp}`
|
|
172
|
+
}]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POI (Point of Interaction) tools for ISV partners
|
|
3
|
+
* Enables management of physical payment points with dynamic pricing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { listPOIsTool } from './list-pois.js';
|
|
7
|
+
import { getPOITool } from './get-poi.js';
|
|
8
|
+
import { getPOIStatusTool } from './get-status.js';
|
|
9
|
+
import { setPOIPriceTool } from './set-price.js';
|
|
10
|
+
import { cancelPOIPriceTool } from './cancel-price.js';
|
|
11
|
+
import { getPOIQRCodeTool } from './get-qrcode.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* All POI tools for ISV
|
|
15
|
+
*/
|
|
16
|
+
export const isvPOITools = [
|
|
17
|
+
listPOIsTool,
|
|
18
|
+
getPOITool,
|
|
19
|
+
getPOIStatusTool,
|
|
20
|
+
setPOIPriceTool,
|
|
21
|
+
cancelPOIPriceTool,
|
|
22
|
+
getPOIQRCodeTool
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
listPOIsTool,
|
|
27
|
+
getPOITool,
|
|
28
|
+
getPOIStatusTool,
|
|
29
|
+
setPOIPriceTool,
|
|
30
|
+
cancelPOIPriceTool,
|
|
31
|
+
getPOIQRCodeTool
|
|
32
|
+
};
|
|
@@ -0,0 +1,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
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List all POIs for a merchant via ISV authentication
|
|
7
|
+
* @param {Object} params - Parameters for list request
|
|
8
|
+
* @returns {Object} List of POIs response
|
|
9
|
+
*/
|
|
10
|
+
export async function listPOIs({ merchantPartnerId, oauth2Token, useSandbox = true }) {
|
|
11
|
+
if (!merchantPartnerId) {
|
|
12
|
+
throw new Error('Merchant Partner ID is required for ISV operations');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!oauth2Token) {
|
|
16
|
+
throw new Error('OAuth2 token is required for ISV operations');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Get ISV credentials
|
|
20
|
+
const isvPartnerId = getPartnerIdSafe();
|
|
21
|
+
const privateKey = getPrivateKeySafe(useSandbox);
|
|
22
|
+
|
|
23
|
+
// Create ISV JWT token (GET request - no body)
|
|
24
|
+
const jwtData = await createJWTForPartner({
|
|
25
|
+
partnerId: isvPartnerId,
|
|
26
|
+
privateKey,
|
|
27
|
+
requestBody: null,
|
|
28
|
+
merchantId: merchantPartnerId,
|
|
29
|
+
oauth2Token
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Required headers as per payware API documentation
|
|
33
|
+
const headers = {
|
|
34
|
+
'Authorization': `Bearer ${jwtData.token}`,
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
'Api-Version': '1'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
|
|
41
|
+
const response = await axios.get(`${baseUrl}/poi`, { headers });
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
pois: response.data,
|
|
46
|
+
count: Array.isArray(response.data) ? response.data.length : 0,
|
|
47
|
+
requestId: response.headers['x-request-id'],
|
|
48
|
+
timestamp: new Date().toISOString()
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: {
|
|
54
|
+
message: error.response?.data?.message || error.message,
|
|
55
|
+
status: error.response?.status,
|
|
56
|
+
code: error.response?.data?.code,
|
|
57
|
+
details: error.response?.data
|
|
58
|
+
},
|
|
59
|
+
timestamp: new Date().toISOString()
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* List POIs tool implementation for ISV
|
|
66
|
+
*/
|
|
67
|
+
export const listPOIsTool = {
|
|
68
|
+
name: "payware_poi_list",
|
|
69
|
+
description: `List all POIs (Points of Interaction) for a merchant.
|
|
70
|
+
|
|
71
|
+
**ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
|
|
72
|
+
**Endpoint:** GET /poi
|
|
73
|
+
**Use Case:** Retrieve all physical payment points configured for a merchant.
|
|
74
|
+
|
|
75
|
+
POI states: IDLE (ready), READY (price set), BUSY (payment in progress), DISABLED.
|
|
76
|
+
|
|
77
|
+
**Required:** Merchant Partner ID and OAuth2 token for ISV authentication.`,
|
|
78
|
+
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
required: ["merchantPartnerId", "oauth2Token"],
|
|
82
|
+
properties: {
|
|
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 { merchantPartnerId, oauth2Token, useSandbox = true } = args;
|
|
101
|
+
|
|
102
|
+
if (!merchantPartnerId) {
|
|
103
|
+
throw new Error("Merchant Partner ID is required for ISV operations");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!oauth2Token) {
|
|
107
|
+
throw new Error("OAuth2 token is required for ISV operations");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = await listPOIs({ merchantPartnerId, oauth2Token, useSandbox });
|
|
111
|
+
|
|
112
|
+
if (result.success) {
|
|
113
|
+
const poisList = result.pois.map(poi => {
|
|
114
|
+
const statusEmoji = {
|
|
115
|
+
'IDLE': '🟢',
|
|
116
|
+
'READY': '🟡',
|
|
117
|
+
'BUSY': '🔴',
|
|
118
|
+
'DISABLED': '⚫'
|
|
119
|
+
}[poi.status] || '⚪';
|
|
120
|
+
|
|
121
|
+
return `${statusEmoji} **${poi.name}** (${poi.poiId})
|
|
122
|
+
Shop: ${poi.shopName || poi.shopCode}
|
|
123
|
+
Status: ${poi.status}${poi.pendingAmount && poi.pendingAmount !== '0.00' ? ` | Pending: ${poi.pendingAmount} ${poi.pendingCurrency}` : ''}`;
|
|
124
|
+
}).join('\n\n');
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
content: [{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: `📍 **POIs Retrieved (ISV -> Merchant: ${merchantPartnerId})**
|
|
130
|
+
|
|
131
|
+
**Total POIs:** ${result.count}
|
|
132
|
+
|
|
133
|
+
${poisList || 'No POIs found for this merchant.'}
|
|
134
|
+
|
|
135
|
+
**Status Legend:**
|
|
136
|
+
🟢 IDLE - Ready for new payment
|
|
137
|
+
🟡 READY - Price set, waiting for customer
|
|
138
|
+
🔴 BUSY - Payment in progress
|
|
139
|
+
⚫ DISABLED - Not accepting payments
|
|
140
|
+
|
|
141
|
+
**Request ID:** ${result.requestId || 'N/A'}
|
|
142
|
+
**Timestamp:** ${result.timestamp}`
|
|
143
|
+
}]
|
|
144
|
+
};
|
|
145
|
+
} else {
|
|
146
|
+
return {
|
|
147
|
+
content: [{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: `❌ **Failed to List POIs**
|
|
150
|
+
|
|
151
|
+
**ISV -> Merchant:** ${getPartnerIdSafe()} -> ${merchantPartnerId}
|
|
152
|
+
|
|
153
|
+
**Error Details:**
|
|
154
|
+
- Message: ${result.error.message}
|
|
155
|
+
- Status: ${result.error.status || 'N/A'}
|
|
156
|
+
- Code: ${result.error.code || 'N/A'}
|
|
157
|
+
|
|
158
|
+
**Troubleshooting:**
|
|
159
|
+
1. Verify OAuth2 token is valid
|
|
160
|
+
2. Ensure merchant has POI feature enabled
|
|
161
|
+
3. Check ISV authorization for merchant
|
|
162
|
+
|
|
163
|
+
**Timestamp:** ${result.timestamp}`
|
|
164
|
+
}]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
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
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Set price on a POI via ISV authentication
|
|
7
|
+
* @param {Object} params - Parameters for set price request
|
|
8
|
+
* @returns {Object} Set price response
|
|
9
|
+
*/
|
|
10
|
+
export async function setPOIPrice({ poiId, amount, currency, reasonL1, reasonL2, timeToLive, callbackUrl, passbackParams, merchantPartnerId, oauth2Token, useSandbox = true }) {
|
|
11
|
+
if (!poiId) {
|
|
12
|
+
throw new Error('POI ID is required');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!amount) {
|
|
16
|
+
throw new Error('Amount is required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!currency) {
|
|
20
|
+
throw new Error('Currency is required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!reasonL1) {
|
|
24
|
+
throw new Error('Reason (reasonL1) is required');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!merchantPartnerId) {
|
|
28
|
+
throw new Error('Merchant Partner ID is required for ISV operations');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!oauth2Token) {
|
|
32
|
+
throw new Error('OAuth2 token is required for ISV operations');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isvPartnerId = getPartnerIdSafe();
|
|
36
|
+
const privateKey = getPrivateKeySafe(useSandbox);
|
|
37
|
+
|
|
38
|
+
// Build request body
|
|
39
|
+
const requestBody = {
|
|
40
|
+
amount: String(amount),
|
|
41
|
+
currency: currency.toUpperCase(),
|
|
42
|
+
reasonL1
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (reasonL2) requestBody.reasonL2 = reasonL2;
|
|
46
|
+
if (timeToLive) requestBody.timeToLive = timeToLive;
|
|
47
|
+
if (callbackUrl) requestBody.callbackUrl = callbackUrl;
|
|
48
|
+
if (passbackParams) requestBody.passbackParams = passbackParams;
|
|
49
|
+
|
|
50
|
+
const jwtData = await createJWTForPartner({
|
|
51
|
+
partnerId: isvPartnerId,
|
|
52
|
+
privateKey,
|
|
53
|
+
requestBody,
|
|
54
|
+
merchantId: merchantPartnerId,
|
|
55
|
+
oauth2Token
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const headers = {
|
|
59
|
+
'Authorization': `Bearer ${jwtData.token}`,
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'Api-Version': '1'
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const baseUrl = useSandbox ? getSandboxUrl() : getProductionUrl();
|
|
66
|
+
const response = await axios.put(`${baseUrl}/poi/${poiId}/price`, requestBody, { headers });
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
result: response.data,
|
|
71
|
+
requestId: response.headers['x-request-id'],
|
|
72
|
+
timestamp: new Date().toISOString()
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: {
|
|
78
|
+
message: error.response?.data?.message || error.message,
|
|
79
|
+
status: error.response?.status,
|
|
80
|
+
code: error.response?.data?.code,
|
|
81
|
+
details: error.response?.data
|
|
82
|
+
},
|
|
83
|
+
timestamp: new Date().toISOString()
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set POI price tool implementation for ISV
|
|
90
|
+
*/
|
|
91
|
+
export const setPOIPriceTool = {
|
|
92
|
+
name: "payware_poi_set_price",
|
|
93
|
+
description: `Set a pending price on a POI for customer payment.
|
|
94
|
+
|
|
95
|
+
**ISV Authentication:** Uses ISV JWT with merchant partner ID and OAuth2 token.
|
|
96
|
+
**Endpoint:** PUT /poi/{poiId}/price
|
|
97
|
+
**Use Case:** Set the amount a customer should pay when they scan the POI.
|
|
98
|
+
|
|
99
|
+
The POI will transition to READY state and wait for a customer scan until the TTL expires.
|
|
100
|
+
|
|
101
|
+
**Required:** POI ID, amount, currency, reason, Merchant Partner ID, and OAuth2 token.`,
|
|
102
|
+
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
required: ["poiId", "amount", "currency", "reasonL1", "merchantPartnerId", "oauth2Token"],
|
|
106
|
+
properties: {
|
|
107
|
+
poiId: {
|
|
108
|
+
type: "string",
|
|
109
|
+
description: "The POI identifier (format: pi + 8 alphanumeric chars, e.g., piABC12345)"
|
|
110
|
+
},
|
|
111
|
+
amount: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Payment amount (e.g., '25.50')"
|
|
114
|
+
},
|
|
115
|
+
currency: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "ISO 4217 currency code (e.g., 'EUR', 'USD', 'BGN')"
|
|
118
|
+
},
|
|
119
|
+
reasonL1: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Payment description (e.g., 'Table 5', 'Order #123')"
|
|
122
|
+
},
|
|
123
|
+
reasonL2: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "Additional description (optional)"
|
|
126
|
+
},
|
|
127
|
+
timeToLive: {
|
|
128
|
+
type: "integer",
|
|
129
|
+
description: "Seconds until price expires (60-600, default: 120)"
|
|
130
|
+
},
|
|
131
|
+
callbackUrl: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "Override callback URL for this payment (optional)"
|
|
134
|
+
},
|
|
135
|
+
passbackParams: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "Data to pass back in callback (optional, max 200 chars)"
|
|
138
|
+
},
|
|
139
|
+
merchantPartnerId: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Partner ID of the target merchant (8 alphanumeric characters)"
|
|
142
|
+
},
|
|
143
|
+
oauth2Token: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "OAuth2 access token obtained from the merchant"
|
|
146
|
+
},
|
|
147
|
+
useSandbox: {
|
|
148
|
+
type: "boolean",
|
|
149
|
+
description: "Use sandbox environment for testing",
|
|
150
|
+
default: true
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
async handler(args) {
|
|
156
|
+
const { poiId, amount, currency, reasonL1, reasonL2, timeToLive, callbackUrl, passbackParams, merchantPartnerId, oauth2Token, useSandbox = true } = args;
|
|
157
|
+
|
|
158
|
+
if (!poiId) throw new Error("POI ID is required");
|
|
159
|
+
if (!amount) throw new Error("Amount is required");
|
|
160
|
+
if (!currency) throw new Error("Currency is required");
|
|
161
|
+
if (!reasonL1) throw new Error("Reason (reasonL1) is required");
|
|
162
|
+
if (!merchantPartnerId) throw new Error("Merchant Partner ID is required");
|
|
163
|
+
if (!oauth2Token) throw new Error("OAuth2 token is required");
|
|
164
|
+
|
|
165
|
+
const result = await setPOIPrice({
|
|
166
|
+
poiId, amount, currency, reasonL1, reasonL2, timeToLive, callbackUrl, passbackParams,
|
|
167
|
+
merchantPartnerId, oauth2Token, useSandbox
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (result.success) {
|
|
171
|
+
const data = result.result;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
content: [{
|
|
175
|
+
type: "text",
|
|
176
|
+
text: `✅ **Price Set Successfully**
|
|
177
|
+
|
|
178
|
+
**POI ID:** ${data.poiId || poiId}
|
|
179
|
+
**Status:** 🟡 ${data.status || 'READY'}
|
|
180
|
+
|
|
181
|
+
**Payment Details:**
|
|
182
|
+
- Amount: **${data.amount || amount} ${data.currency || currency}**
|
|
183
|
+
- Reason: ${reasonL1}${reasonL2 ? ` - ${reasonL2}` : ''}
|
|
184
|
+
|
|
185
|
+
**Session:**
|
|
186
|
+
- Token: ${data.sessionToken || 'N/A'}
|
|
187
|
+
- Expires: ${data.sessionExpiresAt || 'N/A'}
|
|
188
|
+
|
|
189
|
+
**Next Steps:**
|
|
190
|
+
1. Customer scans the POI (QR code, NFC tag, or barcode)
|
|
191
|
+
2. Customer sees the amount and confirms payment
|
|
192
|
+
3. You receive a callback when payment completes
|
|
193
|
+
|
|
194
|
+
**ISV -> Merchant:** ${getPartnerIdSafe()} -> ${merchantPartnerId}
|
|
195
|
+
**Request ID:** ${result.requestId || 'N/A'}
|
|
196
|
+
**Timestamp:** ${result.timestamp}`
|
|
197
|
+
}]
|
|
198
|
+
};
|
|
199
|
+
} else {
|
|
200
|
+
return {
|
|
201
|
+
content: [{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: `❌ **Failed to Set Price**
|
|
204
|
+
|
|
205
|
+
**POI ID:** ${poiId}
|
|
206
|
+
**Attempted:** ${amount} ${currency}
|
|
207
|
+
|
|
208
|
+
**Error:** ${result.error.message}
|
|
209
|
+
**Code:** ${result.error.code || 'N/A'}
|
|
210
|
+
**Status:** ${result.error.status || 'N/A'}
|
|
211
|
+
|
|
212
|
+
**Common Issues:**
|
|
213
|
+
- INVALID_POI_ID (400): POI ID format invalid (must be pi + 8 alphanumeric chars)
|
|
214
|
+
- POI_NOT_FOUND (404): POI doesn't exist or doesn't belong to merchant
|
|
215
|
+
- POI_DISABLED (409): POI is disabled
|
|
216
|
+
- POI_NOT_IDLE (409): POI already has a pending price (cancel it first)
|
|
217
|
+
- INVALID_AMOUNT (400): Check amount format (e.g., '25.50')
|
|
218
|
+
- UNKNOWN_CURRENCY (400): Invalid currency code
|
|
219
|
+
|
|
220
|
+
**Timestamp:** ${result.timestamp}`
|
|
221
|
+
}]
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
@@ -159,7 +159,7 @@ export const getTransactionStatusTool = {
|
|
|
159
159
|
- ID: ${result.transaction.id || 'N/A'}
|
|
160
160
|
- Type: ${result.transaction.type || 'N/A'}
|
|
161
161
|
- Amount: ${result.transaction.trData ? `${result.transaction.trData.amount} ${result.transaction.trData.currency}` : 'N/A'}
|
|
162
|
-
- Description: ${result.transaction.description || 'N/A'}
|
|
162
|
+
${result.transaction.paymentMethod ? `- Payment Method: ${result.transaction.paymentMethod}\n` : ''}- Description: ${result.transaction.description || 'N/A'}
|
|
163
163
|
- Created: ${result.transaction.createdAt || 'N/A'}
|
|
164
164
|
- Updated: ${result.transaction.updatedAt || 'N/A'}
|
|
165
165
|
|
|
@@ -140,7 +140,8 @@ Requires ISV authentication with merchant partner ID and OAuth2 token.`,
|
|
|
140
140
|
'CANCELLED': '🚫'
|
|
141
141
|
};
|
|
142
142
|
const emoji = statusEmojis[tx.status] || '❓';
|
|
143
|
-
|
|
143
|
+
const paymentInfo = tx.paymentMethod ? ` [${tx.paymentMethod}]` : '';
|
|
144
|
+
return ` ${emoji} **${tx.id}** - ${tx.trData?.amount || '0.00'} ${tx.trData?.currency || 'EUR'}${paymentInfo} - ${tx.status} - ${tx.createdAt || 'N/A'}`;
|
|
144
145
|
};
|
|
145
146
|
|
|
146
147
|
return {
|
package/src/mcp-proxy.js
CHANGED
|
@@ -111,13 +111,13 @@ class PaywareMCPBridge {
|
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
process.on('SIGINT', async () => {
|
|
114
|
-
console.
|
|
114
|
+
console.error('\nShutting down MCP Bridge...');
|
|
115
115
|
await this.server.close();
|
|
116
116
|
process.exit(0);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
process.on('SIGTERM', async () => {
|
|
120
|
-
console.
|
|
120
|
+
console.error('\nShutting down MCP Bridge...');
|
|
121
121
|
await this.server.close();
|
|
122
122
|
process.exit(0);
|
|
123
123
|
});
|
|
@@ -27,8 +27,7 @@ export const exportReportTool = {
|
|
|
27
27
|
},
|
|
28
28
|
privateKey: {
|
|
29
29
|
type: "string",
|
|
30
|
-
description: "RSA private key for authentication. Uses
|
|
31
|
-
default: getPrivateKeySafe()
|
|
30
|
+
description: "RSA private key for authentication. Uses production private key as default (Data API is production-only)."
|
|
32
31
|
}
|
|
33
32
|
},
|
|
34
33
|
required: ["requestId"]
|
|
@@ -39,11 +38,11 @@ export const exportReportTool = {
|
|
|
39
38
|
requestId,
|
|
40
39
|
outputFormat = EXPORT_FORMATS.PDF,
|
|
41
40
|
partnerId = getPartnerIdSafe(),
|
|
42
|
-
privateKey = getPrivateKeySafe()
|
|
41
|
+
privateKey = getPrivateKeySafe(false) // false = production (Data API is production-only)
|
|
43
42
|
} = args;
|
|
44
43
|
|
|
45
44
|
if (!partnerId || !privateKey) {
|
|
46
|
-
throw new Error("Partner ID and private key are required. Set PAYWARE_PARTNER_ID and environment
|
|
45
|
+
throw new Error("Partner ID and private key are required. Set PAYWARE_PARTNER_ID and PAYWARE_PRODUCTION_PRIVATE_KEY_PATH environment variables or provide them as parameters.");
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
try {
|
|
@@ -49,8 +49,7 @@ export const generateReportTool = {
|
|
|
49
49
|
},
|
|
50
50
|
privateKey: {
|
|
51
51
|
type: "string",
|
|
52
|
-
description: "RSA private key for authentication. Uses
|
|
53
|
-
default: getPrivateKeySafe()
|
|
52
|
+
description: "RSA private key for authentication. Uses production private key as default (Data API is production-only)."
|
|
54
53
|
}
|
|
55
54
|
},
|
|
56
55
|
required: ["reportUnitId"]
|
|
@@ -65,11 +64,11 @@ export const generateReportTool = {
|
|
|
65
64
|
locale = LOCALES.ENGLISH_US,
|
|
66
65
|
timeZone = 'GMT',
|
|
67
66
|
partnerId = getPartnerIdSafe(),
|
|
68
|
-
privateKey = getPrivateKeySafe()
|
|
67
|
+
privateKey = getPrivateKeySafe(false) // false = production (Data API is production-only)
|
|
69
68
|
} = args;
|
|
70
69
|
|
|
71
70
|
if (!partnerId || !privateKey) {
|
|
72
|
-
throw new Error("Partner ID and private key are required. Set PAYWARE_PARTNER_ID and environment
|
|
71
|
+
throw new Error("Partner ID and private key are required. Set PAYWARE_PARTNER_ID and PAYWARE_PRODUCTION_PRIVATE_KEY_PATH environment variables or provide them as parameters.");
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
// Check for required date range on premium reports
|