shabaaspay-mcp-server 1.0.1
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/LICENSE +21 -0
- package/dist/api/client.d.ts +60 -0
- package/dist/api/client.js +214 -0
- package/dist/config/index.d.ts +54 -0
- package/dist/config/index.js +79 -0
- package/dist/enricher/action-suggester.d.ts +2 -0
- package/dist/enricher/action-suggester.js +26 -0
- package/dist/enricher/index.d.ts +2 -0
- package/dist/enricher/index.js +166 -0
- package/dist/enricher/status-analyzer.d.ts +6 -0
- package/dist/enricher/status-analyzer.js +71 -0
- package/dist/enricher/summary-generator.d.ts +8 -0
- package/dist/enricher/summary-generator.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +28 -0
- package/dist/security/auth.d.ts +4 -0
- package/dist/security/auth.js +30 -0
- package/dist/security/policy.d.ts +18 -0
- package/dist/security/policy.js +35 -0
- package/dist/security/rate-limiter.d.ts +13 -0
- package/dist/security/rate-limiter.js +55 -0
- package/dist/security/validator.d.ts +6 -0
- package/dist/security/validator.js +22 -0
- package/dist/server/http-server.d.ts +28 -0
- package/dist/server/http-server.js +524 -0
- package/dist/server/stdio-server.d.ts +13 -0
- package/dist/server/stdio-server.js +114 -0
- package/dist/server-http.d.ts +2 -0
- package/dist/server-http.js +27 -0
- package/dist/tools/auth.d.ts +17 -0
- package/dist/tools/auth.js +51 -0
- package/dist/tools/index.d.ts +159 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/payment-agreements.d.ts +68 -0
- package/dist/tools/payment-agreements.js +92 -0
- package/dist/tools/payment-initiations.d.ts +84 -0
- package/dist/tools/payment-initiations.js +162 -0
- package/dist/tools/response-helpers.d.ts +4 -0
- package/dist/tools/response-helpers.js +32 -0
- package/dist/types/index.d.ts +184 -0
- package/dist/types/index.js +80 -0
- package/dist/worker.d.ts +15 -0
- package/dist/worker.js +767 -0
- package/package.json +64 -0
- package/readme.md +113 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ShaBaas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Config } from '../config/index.js';
|
|
2
|
+
import { ApiResponse } from '../types/index.js';
|
|
3
|
+
export declare class ShabaasApiClient {
|
|
4
|
+
private client;
|
|
5
|
+
private config;
|
|
6
|
+
private bearerToken;
|
|
7
|
+
private bearerTokenFetchedAtMs;
|
|
8
|
+
constructor(config: Config);
|
|
9
|
+
private normalizeBearer;
|
|
10
|
+
/**
|
|
11
|
+
* Exchanges the configured UUID for a Bearer token.
|
|
12
|
+
* UUID must be sent only to /api/public/authorization in the Authorization header.
|
|
13
|
+
* The returned token is used for all subsequent API calls.
|
|
14
|
+
*/
|
|
15
|
+
authorize(): Promise<{
|
|
16
|
+
token: string;
|
|
17
|
+
raw: any;
|
|
18
|
+
}>;
|
|
19
|
+
private tokenLooksFresh;
|
|
20
|
+
ensureAuthenticated(): Promise<void>;
|
|
21
|
+
private withAuthRetry;
|
|
22
|
+
getAuthToken(): Promise<{
|
|
23
|
+
token: string;
|
|
24
|
+
fetchedAt: string;
|
|
25
|
+
}>;
|
|
26
|
+
getPaymentAgreement(id: string): Promise<ApiResponse>;
|
|
27
|
+
createPaymentAgreement(data: {
|
|
28
|
+
name: string;
|
|
29
|
+
type: string;
|
|
30
|
+
maximum_amount: string;
|
|
31
|
+
frequency: string;
|
|
32
|
+
number_of_transactions_permitted: number;
|
|
33
|
+
pay_id: string;
|
|
34
|
+
idempotency_key: string;
|
|
35
|
+
}): Promise<ApiResponse>;
|
|
36
|
+
cancelPaymentAgreement(id: string): Promise<ApiResponse>;
|
|
37
|
+
resendPaymentAgreement(id: string): Promise<ApiResponse>;
|
|
38
|
+
initiatePayment(data: {
|
|
39
|
+
payment_agreement_id: string;
|
|
40
|
+
amount: string | number;
|
|
41
|
+
description?: string;
|
|
42
|
+
idempotency_key: string;
|
|
43
|
+
}): Promise<ApiResponse>;
|
|
44
|
+
getPaymentInitiation(id: string): Promise<ApiResponse>;
|
|
45
|
+
initiateDirectDebit(data: {
|
|
46
|
+
payment_agreement_id: string;
|
|
47
|
+
amount: string | number;
|
|
48
|
+
description?: string;
|
|
49
|
+
idempotency_key: string;
|
|
50
|
+
}): Promise<ApiResponse>;
|
|
51
|
+
getPaymentLink(data: {
|
|
52
|
+
amount: string;
|
|
53
|
+
reference?: string;
|
|
54
|
+
}): Promise<ApiResponse>;
|
|
55
|
+
registerWebhook(data: {
|
|
56
|
+
url: string;
|
|
57
|
+
events: string[];
|
|
58
|
+
}): Promise<ApiResponse>;
|
|
59
|
+
listWebhooks(): Promise<ApiResponse>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ShabaasApiClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const index_js_1 = require("../config/index.js");
|
|
9
|
+
class ShabaasApiClient {
|
|
10
|
+
client;
|
|
11
|
+
config;
|
|
12
|
+
bearerToken = null;
|
|
13
|
+
bearerTokenFetchedAtMs = null;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
const baseURL = (0, index_js_1.getApiUrl)(config);
|
|
17
|
+
this.client = axios_1.default.create({
|
|
18
|
+
baseURL,
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
timeout: 30000,
|
|
23
|
+
});
|
|
24
|
+
this.client.interceptors.request.use((cfg) => {
|
|
25
|
+
// Log to stderr to keep STDIO transport stdout clean for MCP JSON
|
|
26
|
+
console.error(`[API Request] ${cfg.method?.toUpperCase()} ${cfg.url}`);
|
|
27
|
+
return cfg;
|
|
28
|
+
}, (error) => {
|
|
29
|
+
console.error('[API Request Error]', error);
|
|
30
|
+
return Promise.reject(error);
|
|
31
|
+
});
|
|
32
|
+
this.client.interceptors.response.use((response) => response, (error) => {
|
|
33
|
+
if (error.response) {
|
|
34
|
+
console.error('[API Response Error]', JSON.stringify({
|
|
35
|
+
status: error.response.status,
|
|
36
|
+
data: error.response.data,
|
|
37
|
+
}, null, 2));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.error('[API Network Error]', error.message);
|
|
41
|
+
}
|
|
42
|
+
return Promise.reject(error);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
normalizeBearer(token) {
|
|
46
|
+
const trimmed = token.trim();
|
|
47
|
+
return trimmed.toLowerCase().startsWith('bearer ')
|
|
48
|
+
? trimmed
|
|
49
|
+
: `Bearer ${trimmed}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Exchanges the configured UUID for a Bearer token.
|
|
53
|
+
* UUID must be sent only to /api/public/authorization in the Authorization header.
|
|
54
|
+
* The returned token is used for all subsequent API calls.
|
|
55
|
+
*/
|
|
56
|
+
async authorize() {
|
|
57
|
+
const response = await this.client.post('/api/public/authorization', null, {
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: this.config.shabaasAuthUuid,
|
|
60
|
+
accept: 'application/json',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const payload = response.data;
|
|
64
|
+
const rawToken = payload?.data?.token ||
|
|
65
|
+
payload?.data?.access_token ||
|
|
66
|
+
payload?.token ||
|
|
67
|
+
payload?.access_token ||
|
|
68
|
+
payload?.token ||
|
|
69
|
+
payload?.access_token;
|
|
70
|
+
if (!rawToken || typeof rawToken !== 'string') {
|
|
71
|
+
throw new Error('Authorization succeeded but no token was returned. Expected token in response at data.token.');
|
|
72
|
+
}
|
|
73
|
+
const token = this.normalizeBearer(rawToken);
|
|
74
|
+
this.bearerToken = token;
|
|
75
|
+
this.bearerTokenFetchedAtMs = Date.now();
|
|
76
|
+
this.client.defaults.headers.common['Authorization'] = token;
|
|
77
|
+
return { token, raw: payload };
|
|
78
|
+
}
|
|
79
|
+
tokenLooksFresh() {
|
|
80
|
+
if (!this.bearerToken || !this.bearerTokenFetchedAtMs)
|
|
81
|
+
return false;
|
|
82
|
+
const ageMs = Date.now() - this.bearerTokenFetchedAtMs;
|
|
83
|
+
const maxAgeMs = this.config.authTokenMaxAgeMinutes * 60 * 1000;
|
|
84
|
+
return ageMs < maxAgeMs;
|
|
85
|
+
}
|
|
86
|
+
async ensureAuthenticated() {
|
|
87
|
+
if (this.tokenLooksFresh())
|
|
88
|
+
return;
|
|
89
|
+
await this.authorize();
|
|
90
|
+
}
|
|
91
|
+
async withAuthRetry(fn) {
|
|
92
|
+
try {
|
|
93
|
+
await this.ensureAuthenticated();
|
|
94
|
+
return await fn();
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const status = err?.response?.status;
|
|
98
|
+
if (status === 401 || status === 403) {
|
|
99
|
+
await this.authorize();
|
|
100
|
+
return await fn();
|
|
101
|
+
}
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async getAuthToken() {
|
|
106
|
+
const { token } = await this.authorize();
|
|
107
|
+
return { token, fetchedAt: new Date().toISOString() };
|
|
108
|
+
}
|
|
109
|
+
async getPaymentAgreement(id) {
|
|
110
|
+
return await this.withAuthRetry(async () => {
|
|
111
|
+
const response = await this.client.get(`/api/public/payment_agreement?id=${encodeURIComponent(id)}`);
|
|
112
|
+
return response.data;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async createPaymentAgreement(data) {
|
|
116
|
+
return await this.withAuthRetry(async () => {
|
|
117
|
+
const body = {
|
|
118
|
+
payment_agreement: {
|
|
119
|
+
name: data.name,
|
|
120
|
+
type: data.type,
|
|
121
|
+
maximum_amount: data.maximum_amount,
|
|
122
|
+
frequency: data.frequency,
|
|
123
|
+
number_of_transactions_permitted: data.number_of_transactions_permitted,
|
|
124
|
+
pay_id: data.pay_id,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
const response = await this.client.post('/api/public/payment_agreement', body, {
|
|
128
|
+
headers: {
|
|
129
|
+
'Idempotency-Key': data.idempotency_key,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return response.data;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async cancelPaymentAgreement(id) {
|
|
136
|
+
return await this.withAuthRetry(async () => {
|
|
137
|
+
const response = await this.client.delete(`/api/public/payment_agreement/cancel?id=${encodeURIComponent(id)}`);
|
|
138
|
+
return response.data;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async resendPaymentAgreement(id) {
|
|
142
|
+
return await this.withAuthRetry(async () => {
|
|
143
|
+
const response = await this.client.patch('/api/public/payment_agreement/resend', { id });
|
|
144
|
+
return response.data;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async initiatePayment(data) {
|
|
148
|
+
return await this.withAuthRetry(async () => {
|
|
149
|
+
const amount = normalizeAmount(data.amount);
|
|
150
|
+
const response = await this.client.post('/api/public/payment_initiation', {
|
|
151
|
+
payment_initiation: {
|
|
152
|
+
payment_agreement_id: data.payment_agreement_id,
|
|
153
|
+
amount,
|
|
154
|
+
description: data.description,
|
|
155
|
+
},
|
|
156
|
+
}, {
|
|
157
|
+
headers: {
|
|
158
|
+
'Idempotency-Key': data.idempotency_key,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
return response.data;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async getPaymentInitiation(id) {
|
|
165
|
+
return await this.withAuthRetry(async () => {
|
|
166
|
+
const response = await this.client.get(`/api/public/payment_initiation?id=${encodeURIComponent(id)}`);
|
|
167
|
+
return response.data;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async initiateDirectDebit(data) {
|
|
171
|
+
return await this.withAuthRetry(async () => {
|
|
172
|
+
const amount = normalizeAmount(data.amount);
|
|
173
|
+
const response = await this.client.post('/api/public/payment_initiation/direct_debit', {
|
|
174
|
+
payment_initiation: {
|
|
175
|
+
payment_agreement_id: data.payment_agreement_id,
|
|
176
|
+
amount,
|
|
177
|
+
description: data.description,
|
|
178
|
+
},
|
|
179
|
+
}, {
|
|
180
|
+
headers: {
|
|
181
|
+
'Idempotency-Key': data.idempotency_key,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
return response.data;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async getPaymentLink(data) {
|
|
188
|
+
return await this.withAuthRetry(async () => {
|
|
189
|
+
const response = await this.client.post('/api/get_url', data);
|
|
190
|
+
return response.data;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async registerWebhook(data) {
|
|
194
|
+
return await this.withAuthRetry(async () => {
|
|
195
|
+
const response = await this.client.post('/api/public/webhooks', data);
|
|
196
|
+
return response.data;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async listWebhooks() {
|
|
200
|
+
return await this.withAuthRetry(async () => {
|
|
201
|
+
const response = await this.client.get('/api/public/webhooks');
|
|
202
|
+
return response.data;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
exports.ShabaasApiClient = ShabaasApiClient;
|
|
207
|
+
function normalizeAmount(amount) {
|
|
208
|
+
const numeric = typeof amount === 'string' ? Number(amount) : amount;
|
|
209
|
+
if (Number.isNaN(numeric)) {
|
|
210
|
+
throw new Error('Amount must be a number');
|
|
211
|
+
}
|
|
212
|
+
const rounded = Math.round(numeric * 100) / 100;
|
|
213
|
+
return rounded.toString();
|
|
214
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration notes
|
|
4
|
+
* - ShaBaas API auth uses a UUID that must ONLY be sent to /api/public/authorization to obtain a Bearer token.
|
|
5
|
+
* - The Bearer token returned by that endpoint is used for all subsequent ShaBaas API calls.
|
|
6
|
+
*
|
|
7
|
+
* Env var compatibility:
|
|
8
|
+
* - Prefer SHABAAS_AUTH_UUID
|
|
9
|
+
* - SHABAAS_API_KEY is accepted as a backwards compatible alias for SHABAAS_AUTH_UUID
|
|
10
|
+
*/
|
|
11
|
+
declare const ConfigSchema: z.ZodObject<{
|
|
12
|
+
environment: z.ZodDefault<z.ZodEnum<["sandbox", "production"]>>;
|
|
13
|
+
shabaasAuthUuid: z.ZodString;
|
|
14
|
+
sandboxUrl: z.ZodDefault<z.ZodString>;
|
|
15
|
+
productionUrl: z.ZodDefault<z.ZodString>;
|
|
16
|
+
httpPort: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
httpHost: z.ZodDefault<z.ZodString>;
|
|
18
|
+
mcpHttpApiKey: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
19
|
+
mcpStdioApiKey: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
20
|
+
allowedOrigins: z.ZodDefault<z.ZodEffects<z.ZodString, string[], string>>;
|
|
21
|
+
rateLimitPerMinute: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
rateLimitPerHour: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
authTokenMaxAgeMinutes: z.ZodDefault<z.ZodNumber>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
environment: "sandbox" | "production";
|
|
26
|
+
shabaasAuthUuid: string;
|
|
27
|
+
sandboxUrl: string;
|
|
28
|
+
productionUrl: string;
|
|
29
|
+
httpPort: number;
|
|
30
|
+
httpHost: string;
|
|
31
|
+
mcpHttpApiKey: string;
|
|
32
|
+
mcpStdioApiKey: string;
|
|
33
|
+
allowedOrigins: string[];
|
|
34
|
+
rateLimitPerMinute: number;
|
|
35
|
+
rateLimitPerHour: number;
|
|
36
|
+
authTokenMaxAgeMinutes: number;
|
|
37
|
+
}, {
|
|
38
|
+
shabaasAuthUuid: string;
|
|
39
|
+
environment?: "sandbox" | "production" | undefined;
|
|
40
|
+
sandboxUrl?: string | undefined;
|
|
41
|
+
productionUrl?: string | undefined;
|
|
42
|
+
httpPort?: number | undefined;
|
|
43
|
+
httpHost?: string | undefined;
|
|
44
|
+
mcpHttpApiKey?: string | undefined;
|
|
45
|
+
mcpStdioApiKey?: string | undefined;
|
|
46
|
+
allowedOrigins?: string | undefined;
|
|
47
|
+
rateLimitPerMinute?: number | undefined;
|
|
48
|
+
rateLimitPerHour?: number | undefined;
|
|
49
|
+
authTokenMaxAgeMinutes?: number | undefined;
|
|
50
|
+
}>;
|
|
51
|
+
export type Config = z.infer<typeof ConfigSchema>;
|
|
52
|
+
export declare function loadConfig(): Config;
|
|
53
|
+
export declare function getApiUrl(config: Config): string;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadConfig = loadConfig;
|
|
7
|
+
exports.getApiUrl = getApiUrl;
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
dotenv_1.default.config();
|
|
11
|
+
/**
|
|
12
|
+
* Configuration notes
|
|
13
|
+
* - ShaBaas API auth uses a UUID that must ONLY be sent to /api/public/authorization to obtain a Bearer token.
|
|
14
|
+
* - The Bearer token returned by that endpoint is used for all subsequent ShaBaas API calls.
|
|
15
|
+
*
|
|
16
|
+
* Env var compatibility:
|
|
17
|
+
* - Prefer SHABAAS_AUTH_UUID
|
|
18
|
+
* - SHABAAS_API_KEY is accepted as a backwards compatible alias for SHABAAS_AUTH_UUID
|
|
19
|
+
*/
|
|
20
|
+
const ConfigSchema = zod_1.z.object({
|
|
21
|
+
environment: zod_1.z.enum(['sandbox', 'production']).default('sandbox'),
|
|
22
|
+
// ShaBaas API authentication
|
|
23
|
+
shabaasAuthUuid: zod_1.z.string().min(1, 'ShaBaas auth UUID is required'),
|
|
24
|
+
// Base URLs (override via env; defaults are placeholders only)
|
|
25
|
+
sandboxUrl: zod_1.z.string().url().default('https://sandbox.example.com'),
|
|
26
|
+
productionUrl: zod_1.z.string().url().default('https://api.example.com'),
|
|
27
|
+
// HTTP mode configuration (optional)
|
|
28
|
+
httpPort: zod_1.z.coerce.number().default(3000),
|
|
29
|
+
httpHost: zod_1.z.string().default('0.0.0.0'),
|
|
30
|
+
// If set, HTTP mode requires: Authorization: Bearer <mcpHttpApiKey>
|
|
31
|
+
mcpHttpApiKey: zod_1.z.string().optional().default(''),
|
|
32
|
+
// STDIO mode optional guard: include authorization field in tool arguments
|
|
33
|
+
mcpStdioApiKey: zod_1.z.string().optional().default(''),
|
|
34
|
+
// Security
|
|
35
|
+
allowedOrigins: zod_1.z.string().transform(str => str.split(',')).default('*'),
|
|
36
|
+
rateLimitPerMinute: zod_1.z.coerce.number().default(60),
|
|
37
|
+
rateLimitPerHour: zod_1.z.coerce.number().default(1000),
|
|
38
|
+
// Token cache settings
|
|
39
|
+
// Used only as a heuristic if the auth response does not include expiry metadata
|
|
40
|
+
authTokenMaxAgeMinutes: zod_1.z.coerce.number().default(50),
|
|
41
|
+
});
|
|
42
|
+
function loadConfig() {
|
|
43
|
+
try {
|
|
44
|
+
const shabaasAuthUuid = process.env.SHABAAS_AUTH_UUID ||
|
|
45
|
+
process.env.SHABAAS_API_KEY || // backwards compatible alias
|
|
46
|
+
'';
|
|
47
|
+
return ConfigSchema.parse({
|
|
48
|
+
environment: process.env.SHABAAS_ENVIRONMENT,
|
|
49
|
+
shabaasAuthUuid,
|
|
50
|
+
sandboxUrl: process.env.SHABAAS_SANDBOX_URL,
|
|
51
|
+
productionUrl: process.env.SHABAAS_PRODUCTION_URL,
|
|
52
|
+
httpPort: process.env.HTTP_PORT,
|
|
53
|
+
httpHost: process.env.HTTP_HOST,
|
|
54
|
+
mcpHttpApiKey: process.env.MCP_HTTP_API_KEY || process.env.HTTP_API_KEY || '',
|
|
55
|
+
mcpStdioApiKey: process.env.MCP_STDIO_API_KEY || process.env.STDIO_API_KEY || '',
|
|
56
|
+
allowedOrigins: process.env.ALLOWED_ORIGINS,
|
|
57
|
+
rateLimitPerMinute: process.env.API_RATE_LIMIT_PER_MINUTE,
|
|
58
|
+
rateLimitPerHour: process.env.API_RATE_LIMIT_PER_HOUR,
|
|
59
|
+
authTokenMaxAgeMinutes: process.env.AUTH_TOKEN_MAX_AGE_MINUTES,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof zod_1.z.ZodError) {
|
|
64
|
+
console.error('Configuration error:');
|
|
65
|
+
error.errors.forEach(err => {
|
|
66
|
+
console.error(` - ${err.path.join('.')}: ${err.message}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.error('Failed to load configuration:', error);
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getApiUrl(config) {
|
|
76
|
+
return config.environment === 'production'
|
|
77
|
+
? config.productionUrl
|
|
78
|
+
: config.sandboxUrl;
|
|
79
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.suggestActions = suggestActions;
|
|
4
|
+
function suggestActions(agreement) {
|
|
5
|
+
const actions = [];
|
|
6
|
+
const status = (agreement.status || "").toLowerCase().trim();
|
|
7
|
+
actions.push("get_payment_agreement");
|
|
8
|
+
if (status === "active") {
|
|
9
|
+
actions.push("initiate_payment");
|
|
10
|
+
actions.push("initiate_direct_debit");
|
|
11
|
+
actions.push("cancel_payment_agreement");
|
|
12
|
+
return actions;
|
|
13
|
+
}
|
|
14
|
+
if (status === "created" || status === "pending") {
|
|
15
|
+
actions.push("resend_payment_agreement");
|
|
16
|
+
actions.push("cancel_payment_agreement");
|
|
17
|
+
actions.push("request_payer_authorisation");
|
|
18
|
+
return actions;
|
|
19
|
+
}
|
|
20
|
+
if (status === "cancelled" || status === "canceled" || status === "expired") {
|
|
21
|
+
actions.push("create_payment_agreement");
|
|
22
|
+
return actions;
|
|
23
|
+
}
|
|
24
|
+
actions.push("manual_review_required");
|
|
25
|
+
return actions;
|
|
26
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enrichPaymentAgreement = enrichPaymentAgreement;
|
|
4
|
+
const status_analyzer_js_1 = require("./status-analyzer.js");
|
|
5
|
+
const action_suggester_js_1 = require("./action-suggester.js");
|
|
6
|
+
const summary_generator_js_1 = require("./summary-generator.js");
|
|
7
|
+
function enrichPaymentAgreement(data, includeRaw, rawResponse, environment) {
|
|
8
|
+
const startTime = Date.now();
|
|
9
|
+
const statusAnalysis = (0, status_analyzer_js_1.analyzeStatus)(data.status, data.extented_status);
|
|
10
|
+
const nextActions = (0, action_suggester_js_1.suggestActions)(data);
|
|
11
|
+
const business = buildBusinessInsights(data, statusAnalysis);
|
|
12
|
+
const summary = (0, summary_generator_js_1.generateSummary)(data, statusAnalysis, business);
|
|
13
|
+
return {
|
|
14
|
+
success: true,
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
data,
|
|
17
|
+
metadata: {
|
|
18
|
+
requestId: generateRequestId(),
|
|
19
|
+
processingTime: Date.now() - startTime,
|
|
20
|
+
environment,
|
|
21
|
+
},
|
|
22
|
+
insights: {
|
|
23
|
+
status: statusAnalysis.displayStatus,
|
|
24
|
+
canProceed: statusAnalysis.canInitiatePayment,
|
|
25
|
+
nextActions,
|
|
26
|
+
warnings: statusAnalysis.warnings,
|
|
27
|
+
business,
|
|
28
|
+
},
|
|
29
|
+
summary,
|
|
30
|
+
...(includeRaw ? { raw: rawResponse } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function generateRequestId() {
|
|
34
|
+
return `req_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
35
|
+
}
|
|
36
|
+
function parseExtendedStatus(ext) {
|
|
37
|
+
if (!ext)
|
|
38
|
+
return {};
|
|
39
|
+
const parts = ext
|
|
40
|
+
.split("-")
|
|
41
|
+
.map((p) => p.trim())
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
if (parts.length === 0)
|
|
44
|
+
return {};
|
|
45
|
+
const maximumAmount = parts[0] && !Number.isNaN(Number(parts[0])) ? Number(parts[0]) : undefined;
|
|
46
|
+
const frequency = parts.length > 1 ? parts[1] : undefined;
|
|
47
|
+
const numberOfTransactionsPermitted = parts.length > 2 && !Number.isNaN(Number(parts[2])) ? Number(parts[2]) : undefined;
|
|
48
|
+
const status = parts.length > 3 ? parts[3] : undefined;
|
|
49
|
+
return { maximumAmount, frequency, numberOfTransactionsPermitted, status };
|
|
50
|
+
}
|
|
51
|
+
function safeDate(value) {
|
|
52
|
+
if (!value)
|
|
53
|
+
return undefined;
|
|
54
|
+
const d = new Date(value);
|
|
55
|
+
if (Number.isNaN(d.getTime()))
|
|
56
|
+
return undefined;
|
|
57
|
+
return d;
|
|
58
|
+
}
|
|
59
|
+
function daysDiff(from, to) {
|
|
60
|
+
return Math.floor((to.getTime() - from.getTime()) / 86400000);
|
|
61
|
+
}
|
|
62
|
+
function buildBusinessInsights(agreement, statusAnalysis) {
|
|
63
|
+
const ext = parseExtendedStatus(agreement.extented_status);
|
|
64
|
+
const status = (agreement.status || "").toLowerCase();
|
|
65
|
+
const canInitiate = !!statusAnalysis.canInitiatePayment;
|
|
66
|
+
const createdAt = safeDate(agreement.created_at);
|
|
67
|
+
const endAt = safeDate(agreement.end_date);
|
|
68
|
+
const now = new Date();
|
|
69
|
+
const createdDaysAgo = createdAt ? Math.abs(daysDiff(createdAt, now)) : undefined;
|
|
70
|
+
const expiresInDays = endAt ? Math.max(0, Math.ceil((endAt.getTime() - now.getTime()) / 86400000)) : undefined;
|
|
71
|
+
const isExpiringSoon = typeof expiresInDays === "number" ? expiresInDays <= 30 : undefined;
|
|
72
|
+
const maximumAmountFromField = agreement.maximum_amount && !Number.isNaN(Number(agreement.maximum_amount))
|
|
73
|
+
? Number(agreement.maximum_amount)
|
|
74
|
+
: undefined;
|
|
75
|
+
const maximumAmount = maximumAmountFromField ?? ext.maximumAmount;
|
|
76
|
+
const meaning = canInitiate
|
|
77
|
+
? "Agreement is active and ready for real time payment initiation"
|
|
78
|
+
: status === "created" || status === "pending"
|
|
79
|
+
? "Agreement is created but not yet authorised by the payer"
|
|
80
|
+
: status === "suspended"
|
|
81
|
+
? "Agreement is suspended and payments should not be initiated"
|
|
82
|
+
: status === "cancelled" || status === "canceled"
|
|
83
|
+
? "Agreement is cancelled and cannot be used for further payments"
|
|
84
|
+
: status === "expired"
|
|
85
|
+
? "Agreement is expired and cannot be used for further payments"
|
|
86
|
+
: "Agreement status requires manual review before proceeding";
|
|
87
|
+
const readinessReason = canInitiate
|
|
88
|
+
? "Agreement is active so payment initiation can proceed within the agreed limit"
|
|
89
|
+
: status === "created" || status === "pending"
|
|
90
|
+
? "Agreement exists but payer must authorise it in their banking app before any debit can occur"
|
|
91
|
+
: status === "suspended"
|
|
92
|
+
? "Agreement is suspended so payment initiation should not proceed"
|
|
93
|
+
: status === "cancelled" || status === "canceled"
|
|
94
|
+
? "Agreement is cancelled so a new agreement is required"
|
|
95
|
+
: status === "expired"
|
|
96
|
+
? "Agreement is expired so a new agreement is required"
|
|
97
|
+
: "Agreement is not in a state that supports payment initiation";
|
|
98
|
+
const payerControls = [
|
|
99
|
+
"Payer authorises PayTo agreements in online banking before a business can take a payment",
|
|
100
|
+
"Payer can view agreements and pause, resume or cancel them in online banking",
|
|
101
|
+
];
|
|
102
|
+
const reconciliationNotes = [
|
|
103
|
+
"PayTo agreements carry richer agreement data which can support reconciliation and reduce manual matching",
|
|
104
|
+
];
|
|
105
|
+
const operationalChecks = [];
|
|
106
|
+
if (canInitiate) {
|
|
107
|
+
operationalChecks.push("Initiate the payment then verify payment initiation status");
|
|
108
|
+
operationalChecks.push("If a payer reports an unexpected debit remind them they can review agreement details in their banking app");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
operationalChecks.push("Ask the payer to authorise the agreement in their banking app then re check agreement status");
|
|
112
|
+
operationalChecks.push("If the payer cannot locate the agreement ask them to check PayTo agreements in their banking app");
|
|
113
|
+
}
|
|
114
|
+
if (isExpiringSoon) {
|
|
115
|
+
operationalChecks.push("Agreement is expiring soon. Consider issuing a new agreement to avoid collection disruption");
|
|
116
|
+
}
|
|
117
|
+
const riskFlags = [];
|
|
118
|
+
if (typeof maximumAmount === "number" && maximumAmount <= 0) {
|
|
119
|
+
riskFlags.push("Maximum amount is zero or negative");
|
|
120
|
+
}
|
|
121
|
+
if (typeof maximumAmount === "number" && maximumAmount < 2) {
|
|
122
|
+
riskFlags.push("Very low maximum amount. Confirm commercial intent");
|
|
123
|
+
}
|
|
124
|
+
if (isExpiringSoon) {
|
|
125
|
+
riskFlags.push("Agreement expiring soon");
|
|
126
|
+
}
|
|
127
|
+
const frequencyLabel = ext.frequency ? ext.frequency.toLowerCase() : undefined;
|
|
128
|
+
const recommendedCustomerCopy = canInitiate
|
|
129
|
+
? `Your PayTo agreement is active. We will only debit up to AUD ${typeof maximumAmount === "number" ? maximumAmount.toFixed(2) : ""}${frequencyLabel ? " " + frequencyLabel : ""}. You can manage or cancel the agreement in your banking app.`
|
|
130
|
+
: "Please authorise this PayTo agreement in your banking app so we can initiate payments within the agreed limit.";
|
|
131
|
+
const references = [
|
|
132
|
+
{ title: "PayTo overview", url: "https://www.auspayplus.com.au/solutions/payto" },
|
|
133
|
+
{ title: "PayTo FAQs", url: "https://www.auspayplus.com.au/solutions/payto-faqs" },
|
|
134
|
+
{ title: "PayTo for consumers", url: "https://www.auspayplus.com.au/solutions/payto-for-consumers" },
|
|
135
|
+
{ title: "PayTo business use cases", url: "https://www.auspayplus.com.au/solutions/payto-for-businesses-use-cases" },
|
|
136
|
+
];
|
|
137
|
+
return {
|
|
138
|
+
product: "PayTo",
|
|
139
|
+
meaning,
|
|
140
|
+
readiness: {
|
|
141
|
+
canInitiatePayment: canInitiate,
|
|
142
|
+
reason: readinessReason,
|
|
143
|
+
},
|
|
144
|
+
limits: {
|
|
145
|
+
maximumAmount,
|
|
146
|
+
currency: "AUD",
|
|
147
|
+
},
|
|
148
|
+
cadence: {
|
|
149
|
+
frequency: ext.frequency,
|
|
150
|
+
numberOfTransactionsPermitted: ext.numberOfTransactionsPermitted,
|
|
151
|
+
},
|
|
152
|
+
timing: {
|
|
153
|
+
createdAt: agreement.created_at,
|
|
154
|
+
endDate: agreement.end_date,
|
|
155
|
+
createdDaysAgo,
|
|
156
|
+
expiresInDays,
|
|
157
|
+
isExpiringSoon,
|
|
158
|
+
},
|
|
159
|
+
payerControls,
|
|
160
|
+
reconciliationNotes,
|
|
161
|
+
operationalChecks,
|
|
162
|
+
recommendedCustomerCopy,
|
|
163
|
+
riskFlags,
|
|
164
|
+
references,
|
|
165
|
+
};
|
|
166
|
+
}
|