@payware/mcp-server 1.3.2 → 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.
@@ -1,314 +1,323 @@
1
- /**
2
- * Simulate callback scenarios for testing
3
- */
4
-
5
- /**
6
- * Generate mock callback payload
7
- * @param {string} transactionId - Transaction ID
8
- * @param {string} status - Transaction status (CONFIRMED, DECLINED, FAILED, EXPIRED, CANCELLED)
9
- * @param {Object} options - Additional options
10
- * @returns {Object} Mock callback payload
11
- */
12
- export function generateMockCallback(transactionId, status = 'CONFIRMED', options = {}) {
13
- const {
14
- amount = '57.60',
15
- currency = 'EUR',
16
- payerAmount = null,
17
- payeeAmount = null,
18
- paymentMethod = null,
19
- statusMessage = null,
20
- passbackParams = null,
21
- // The fee breakdown the server attaches whenever the transaction has a fee configuration.
22
- // Before 2026-08-21 a merchant's FINALIZED callback carried the total fee but not the two
23
- // numbers that produced it, so reconciling a charge meant asking payware. It carries both now.
24
- feeFixed = '0.1000',
25
- feeRate = '0.0150',
26
- // Pass fee: null to model the anomaly the server reports by OMITTING the field - a transaction
27
- // with no recorded fee. That is deliberately distinguishable from a genuine "0.00".
28
- fee: feeOverride = undefined
29
- } = options;
30
-
31
- // Calculate fee like the server: |payerAmount - amount| or |payeeAmount - amount|.
32
- const transactionAmount = parseFloat(amount);
33
- let fee = '0.00';
34
- if (payerAmount !== null) {
35
- fee = Math.abs(parseFloat(payerAmount) - transactionAmount).toFixed(2);
36
- } else if (payeeAmount !== null) {
37
- fee = Math.abs(parseFloat(payeeAmount) - transactionAmount).toFixed(2);
38
- }
39
- if (feeOverride !== undefined) {
40
- fee = feeOverride;
41
- }
42
-
43
- const currentTime = Date.now();
44
- // Built with the same omission rule the server applies. TransactionCallbackPayload is
45
- // @JsonInclude(NON_NULL), so a property with no value is ABSENT from the JSON rather than sent as
46
- // null or 0 - and a consumer is entitled to tell those apart. Emitting "passbackParams": null or
47
- // "fee": "0.0000" here would have taught an integrator to expect a shape the server never sends.
48
- const basePayload = {
49
- callbackType: 'TRANSACTION_FINALIZED',
50
- transactionId,
51
- ...(passbackParams !== null && passbackParams !== undefined && { passbackParams }),
52
- amount,
53
- ...(fee !== null && fee !== undefined && { fee }),
54
- ...(feeFixed !== null && feeFixed !== undefined && { feeFixed }),
55
- ...(feeRate !== null && feeRate !== undefined && { feeRate }),
56
- currency,
57
- status,
58
- ...(statusMessage !== null && statusMessage !== undefined && { statusMessage }),
59
- ...(paymentMethod && { paymentMethod }),
60
- created: currentTime - 300000, // Created 5 minutes ago (milliseconds)
61
- // Absent on EXPIRED: the expiry sweep transitions status in bulk and sets no finalized moment.
62
- ...(status !== 'EXPIRED' && { finalized: currentTime })
63
- };
64
-
65
- // Add status-specific statusMessage
66
- switch (status) {
67
- case 'CONFIRMED':
68
- return {
69
- ...basePayload,
70
- statusMessage: statusMessage || 'Transaction confirmed'
71
- };
72
-
73
- case 'DECLINED':
74
- return {
75
- ...basePayload,
76
- statusMessage: statusMessage || 'Insufficient funds'
77
- };
78
-
79
- case 'FAILED':
80
- return {
81
- ...basePayload,
82
- statusMessage: statusMessage || 'Payment processing failed'
83
- };
84
-
85
- case 'EXPIRED':
86
- return {
87
- ...basePayload,
88
- statusMessage: statusMessage || 'Transaction expired',
89
- finalized: undefined // Remove finalized for expired transactions
90
- };
91
-
92
- case 'CANCELLED':
93
- return {
94
- ...basePayload,
95
- statusMessage: statusMessage || 'Transaction cancelled'
96
- };
97
-
98
- default:
99
- return basePayload;
100
- }
101
- }
102
-
103
- /**
104
- * Simulate callback delivery
105
- * @param {string} callbackUrl - URL to send callback to
106
- * @param {Object} payload - Callback payload
107
- * @returns {Object} Simulation result
108
- */
109
- export async function simulateCallbackDelivery(callbackUrl, payload) {
110
- if (!callbackUrl || !callbackUrl.startsWith('http')) {
111
- return {
112
- success: false,
113
- error: 'Invalid callback URL provided'
114
- };
115
- }
116
-
117
- // In a real implementation, this would actually send HTTP POST
118
- // For simulation, we just return what would be sent
119
- return {
120
- success: true,
121
- url: callbackUrl,
122
- payload,
123
- headers: {
124
- 'Content-Type': 'application/json',
125
- 'Authorization': 'Bearer mock-jwt-token-' + Date.now()
126
- },
127
- simulatedAt: new Date().toISOString(),
128
- note: 'This is a simulation - no actual HTTP request was made. Real payware callbacks use JWT tokens in Authorization header.'
129
- };
130
- }
131
-
132
- /**
133
- * Simulate callback tool implementation
134
- */
135
- export const simulateCallbackTool = {
136
- name: "payware_operations_simulate_callback",
137
- description: `Simulate callback scenarios for testing webhook handling.
138
-
139
- Generates the payload a merchant's \`callbackUrl\` actually receives, so a handler can be built and
140
- tested against the real shape. Four properties of that shape are easy to get wrong:
141
-
142
- **1. Absent means absent, not zero.** The payload omits properties with no value rather than sending
143
- \`null\` or \`0\`. \`statusMessage\`, \`passbackParams\`, \`fee\`, \`feeFixed\` and \`feeRate\` are all simply
144
- missing when they do not apply. A missing \`fee\` means the fee is *unknown* for that transaction - it
145
- does not mean the transaction was free. Do not write a handler that reads \`payload.fee\` as a number
146
- without checking it is there.
147
-
148
- **2. \`finalized\` is absent on EXPIRED.** An expired transaction is closed by the platform's expiry
149
- sweep, which sets no finalization moment. Every other status carries one.
150
-
151
- **3. \`EXPIRED\` is a non-payment, not a failure.** The payer never paid within \`timeToLive\`. Release
152
- the order or the stock, as you would for a cancellation - do not treat it as an error to retry.
153
-
154
- **4. Deduplicate on \`(transactionId, callbackType)\`.** payware retries a callback once a second, up
155
- to 15 times, until the endpoint answers 200. That pair is stable across every retry of the same
156
- event, and it is the key payware guarantees. A handler that does not deduplicate will eventually
157
- process one payment twice.
158
-
159
- **Not on this payload:** \`deliveryAddress\` (removed 2026-08-07 - fetch it from
160
- \`payware_operations_get_transaction_history\`, which is authenticated and tenant-scoped),
161
- \`producerPartnerId\`, \`transactionType\` and \`initiatedBy\` (the last two are carried on
162
- TRANSACTION_PROCESSED, which merchants do not receive).`,
163
- inputSchema: {
164
- type: "object",
165
- properties: {
166
- transactionId: {
167
- type: "string",
168
- description: "Transaction ID to simulate callback for"
169
- },
170
- status: {
171
- type: "string",
172
- enum: ["CONFIRMED", "DECLINED", "FAILED", "EXPIRED", "CANCELLED"],
173
- description: "Transaction status to simulate",
174
- default: "CONFIRMED"
175
- },
176
- callbackUrl: {
177
- type: "string",
178
- description: "URL where callback would be sent (for simulation only)",
179
- format: "uri"
180
- },
181
- amount: {
182
- type: "string",
183
- description: "Transaction amount (e.g., '57.60')",
184
- default: "57.60"
185
- },
186
- currency: {
187
- type: "string",
188
- enum: ["EUR", "USD", "GBP"],
189
- description: "Transaction currency",
190
- default: "EUR"
191
- },
192
- fee: {
193
- type: "string",
194
- description: "The payware fee on this transaction, at the currency's smallest payable unit (e.g. '0.29'). Not always two decimals - it follows the currency. Pass null to model the anomaly case, where a transaction has no recorded fee and the server OMITS the property entirely; that is deliberately distinguishable from a genuine '0.00'."
195
- },
196
- feeFixed: {
197
- type: "string",
198
- description: "Fixed fee component of the applicable fee configuration, e.g. '0.1000'. Informational, for reconciliation. Present on FINALIZED callbacks since 2026-08-21 - before that a merchant got the total fee but not the breakdown that produced it. Pass null to model a transaction with no fee configuration, where it is omitted.",
199
- default: "0.1000"
200
- },
201
- feeRate: {
202
- type: "string",
203
- description: "Variable rate of the applicable fee configuration, e.g. '0.0150' = 1.5%. Informational, same as feeFixed. Pass null to model a transaction with no fee configuration.",
204
- default: "0.0150"
205
- },
206
- statusMessage: {
207
- type: "string",
208
- description: "Custom status message for the transaction callback"
209
- },
210
- paymentMethod: {
211
- type: "string",
212
- enum: ["A2A", "CARD_FUNDED", "BNPL", "INSTANT_CREDIT"],
213
- description: "Payment method chosen by customer. A2A = direct transfer. CARD_FUNDED = card-linked account. BNPL = buy now pay later. INSTANT_CREDIT = credit line."
214
- }
215
- },
216
- required: ["transactionId"],
217
- additionalProperties: false
218
- },
219
-
220
- async handler(args) {
221
- const {
222
- transactionId,
223
- status = 'CONFIRMED',
224
- callbackUrl,
225
- amount = '57.60',
226
- currency = 'EUR',
227
- // No default: an omitted fee falls through to generateMockCallback's own derivation.
228
- // Defaulting it to '0.00' here made every simulated callback assert a zero fee, which is the
229
- // one value the real payload never sends for "no fee" - it omits the property instead.
230
- fee,
231
- feeFixed = '0.1000',
232
- feeRate = '0.0150',
233
- statusMessage,
234
- paymentMethod
235
- } = args;
236
-
237
- if (!transactionId) {
238
- throw new Error("Transaction ID is required");
239
- }
240
-
241
- // Generate mock callback payload
242
- const payload = generateMockCallback(transactionId, status, {
243
- amount,
244
- currency,
245
- fee,
246
- feeFixed,
247
- feeRate,
248
- statusMessage,
249
- paymentMethod
250
- });
251
-
252
- // Simulate callback delivery if URL provided
253
- let deliveryResult = null;
254
- if (callbackUrl) {
255
- deliveryResult = await simulateCallbackDelivery(callbackUrl, payload);
256
- }
257
-
258
- const statusEmojis = {
259
- 'CONFIRMED': '✅',
260
- 'DECLINED': '❌',
261
- 'FAILED': '💥',
262
- 'EXPIRED': '⏰',
263
- 'CANCELLED': '🚫'
264
- };
265
-
266
- const statusEmoji = statusEmojis[status] || '❓';
267
-
268
- return {
269
- content: [{
270
- type: "text",
271
- text: `🔄 **Callback Simulation Complete**
272
-
273
- **Transaction:** ${transactionId}
274
- **Simulated Status:** ${statusEmoji} ${status}
275
-
276
- **Mock Callback Payload:**
277
- \`\`\`json
278
- ${JSON.stringify(payload, null, 2)}
279
- \`\`\`
280
-
281
- ${deliveryResult ? `
282
- **Callback Delivery Simulation:**
283
- ${deliveryResult.success ? '✅ Callback would be delivered successfully' : '❌ Callback delivery would fail'}
284
-
285
- **Delivery Details:**
286
- - URL: ${deliveryResult.url || 'N/A'}
287
- - Headers: ${JSON.stringify(deliveryResult.headers || {}, null, 2)}
288
- - Simulated At: ${deliveryResult.simulatedAt || 'N/A'}
289
-
290
- **Note:** ${deliveryResult.note || 'No actual HTTP request was made'}
291
- ` : '**No callback URL provided** - only payload generated'}
292
-
293
- **Status Explanations:**
294
- - ✅ **CONFIRMED**: Successfully finalized
295
- - ❌ **DECLINED**: Declined by the user, processing or finalizing payment institutions
296
- - 💥 **FAILED**: Failed due to technical reasons or other
297
- - ⏰ **EXPIRED**: Time to live of the transaction has passed
298
- - 🚫 **CANCELLED**: Transaction canceled by the originator
299
-
300
- **Integration Testing:**
301
- 1. Use this payload to test your callback handler
302
- 2. Verify your system processes each status correctly
303
- 3. Check error handling for DECLINED/FAILED statuses
304
- 4. Ensure proper logging and monitoring
305
-
306
- **Next Steps:**
307
- 1. Implement callback endpoint to receive this payload
308
- 2. Test with different status scenarios
309
- 3. Add signature verification in production
310
- 4. Set up proper error handling and retry logic`
311
- }]
312
- };
313
- }
1
+ /**
2
+ * Simulate callback scenarios for testing
3
+ */
4
+
5
+ /**
6
+ * Generate mock callback payload
7
+ * @param {string} transactionId - Transaction ID
8
+ * @param {string} status - Transaction status (CONFIRMED, DECLINED, FAILED, EXPIRED, CANCELLED)
9
+ * @param {Object} options - Additional options
10
+ * @returns {Object} Mock callback payload
11
+ */
12
+ export function generateMockCallback(transactionId, status = 'CONFIRMED', options = {}) {
13
+ const {
14
+ amount = '57.60',
15
+ currency = 'EUR',
16
+ payerAmount = null,
17
+ payeeAmount = null,
18
+ paymentMethod = null,
19
+ statusMessage = null,
20
+ passbackParams = null,
21
+ // The POI that created the transaction, on a POI sale's callbacks only (server S8, 2026-09-28).
22
+ poiId = null,
23
+ // The fee breakdown the server attaches whenever the transaction has a fee configuration.
24
+ // Before 2026-08-21 a merchant's FINALIZED callback carried the total fee but not the two
25
+ // numbers that produced it, so reconciling a charge meant asking payware. It carries both now.
26
+ feeFixed = '0.1000',
27
+ feeRate = '0.0150',
28
+ // Pass fee: null to model the anomaly the server reports by OMITTING the field - a transaction
29
+ // with no recorded fee. That is deliberately distinguishable from a genuine "0.00".
30
+ fee: feeOverride = undefined
31
+ } = options;
32
+
33
+ // Calculate fee like the server: |payerAmount - amount| or |payeeAmount - amount|.
34
+ const transactionAmount = parseFloat(amount);
35
+ let fee = '0.00';
36
+ if (payerAmount !== null) {
37
+ fee = Math.abs(parseFloat(payerAmount) - transactionAmount).toFixed(2);
38
+ } else if (payeeAmount !== null) {
39
+ fee = Math.abs(parseFloat(payeeAmount) - transactionAmount).toFixed(2);
40
+ }
41
+ if (feeOverride !== undefined) {
42
+ fee = feeOverride;
43
+ }
44
+
45
+ const currentTime = Date.now();
46
+ // Built with the same omission rule the server applies. TransactionCallbackPayload is
47
+ // @JsonInclude(NON_NULL), so a property with no value is ABSENT from the JSON rather than sent as
48
+ // null or 0 - and a consumer is entitled to tell those apart. Emitting "passbackParams": null or
49
+ // "fee": "0.0000" here would have taught an integrator to expect a shape the server never sends.
50
+ const basePayload = {
51
+ callbackType: 'TRANSACTION_FINALIZED',
52
+ transactionId,
53
+ ...(poiId && { poiId }),
54
+ ...(passbackParams !== null && passbackParams !== undefined && { passbackParams }),
55
+ amount,
56
+ ...(fee !== null && fee !== undefined && { fee }),
57
+ ...(feeFixed !== null && feeFixed !== undefined && { feeFixed }),
58
+ ...(feeRate !== null && feeRate !== undefined && { feeRate }),
59
+ currency,
60
+ status,
61
+ ...(statusMessage !== null && statusMessage !== undefined && { statusMessage }),
62
+ ...(paymentMethod && { paymentMethod }),
63
+ created: currentTime - 300000, // Created 5 minutes ago (milliseconds)
64
+ // Absent on EXPIRED: the expiry sweep transitions status in bulk and sets no finalized moment.
65
+ ...(status !== 'EXPIRED' && { finalized: currentTime })
66
+ };
67
+
68
+ // Add status-specific statusMessage
69
+ switch (status) {
70
+ case 'CONFIRMED':
71
+ return {
72
+ ...basePayload,
73
+ statusMessage: statusMessage || 'Transaction confirmed'
74
+ };
75
+
76
+ case 'DECLINED':
77
+ return {
78
+ ...basePayload,
79
+ statusMessage: statusMessage || 'Insufficient funds'
80
+ };
81
+
82
+ case 'FAILED':
83
+ return {
84
+ ...basePayload,
85
+ statusMessage: statusMessage || 'Payment processing failed'
86
+ };
87
+
88
+ case 'EXPIRED':
89
+ return {
90
+ ...basePayload,
91
+ statusMessage: statusMessage || 'Transaction expired',
92
+ finalized: undefined // Remove finalized for expired transactions
93
+ };
94
+
95
+ case 'CANCELLED':
96
+ return {
97
+ ...basePayload,
98
+ statusMessage: statusMessage || 'Transaction cancelled'
99
+ };
100
+
101
+ default:
102
+ return basePayload;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Simulate callback delivery
108
+ * @param {string} callbackUrl - URL to send callback to
109
+ * @param {Object} payload - Callback payload
110
+ * @returns {Object} Simulation result
111
+ */
112
+ export async function simulateCallbackDelivery(callbackUrl, payload) {
113
+ if (!callbackUrl || !callbackUrl.startsWith('http')) {
114
+ return {
115
+ success: false,
116
+ error: 'Invalid callback URL provided'
117
+ };
118
+ }
119
+
120
+ // In a real implementation, this would actually send HTTP POST
121
+ // For simulation, we just return what would be sent
122
+ return {
123
+ success: true,
124
+ url: callbackUrl,
125
+ payload,
126
+ headers: {
127
+ 'Content-Type': 'application/json',
128
+ 'Authorization': 'Bearer mock-jwt-token-' + Date.now()
129
+ },
130
+ simulatedAt: new Date().toISOString(),
131
+ note: 'This is a simulation - no actual HTTP request was made. Real payware callbacks use JWT tokens in Authorization header.'
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Simulate callback tool implementation
137
+ */
138
+ export const simulateCallbackTool = {
139
+ name: "payware_operations_simulate_callback",
140
+ description: `Simulate callback scenarios for testing webhook handling.
141
+
142
+ Generates the payload a merchant's \`callbackUrl\` actually receives, so a handler can be built and
143
+ tested against the real shape. Four properties of that shape are easy to get wrong:
144
+
145
+ **1. Absent means absent, not zero.** The payload omits properties with no value rather than sending
146
+ \`null\` or \`0\`. \`statusMessage\`, \`passbackParams\`, \`fee\`, \`feeFixed\` and \`feeRate\` are all simply
147
+ missing when they do not apply. A missing \`fee\` means the fee is *unknown* for that transaction - it
148
+ does not mean the transaction was free. Do not write a handler that reads \`payload.fee\` as a number
149
+ without checking it is there.
150
+
151
+ **2. \`finalized\` is absent on EXPIRED.** An expired transaction is closed by the platform's expiry
152
+ sweep, which sets no finalization moment. Every other status carries one.
153
+
154
+ **3. \`EXPIRED\` is a non-payment, not a failure.** The payer never paid within \`timeToLive\`. Release
155
+ the order or the stock, as you would for a cancellation - do not treat it as an error to retry.
156
+
157
+ **4. Deduplicate on \`(transactionId, callbackType)\`.** payware retries a callback up to 15 times,
158
+ backing off from about a second to at most 5 minutes between attempts, until the endpoint answers any
159
+ 2xx. That pair is stable across every retry of the same event, and it is the key payware guarantees.
160
+ A handler that does not deduplicate will eventually process one payment twice.
161
+
162
+ **Not on this payload:** \`deliveryAddress\` (removed 2026-08-07 - fetch it from
163
+ \`payware_operations_get_transaction_history\`, which is authenticated and tenant-scoped),
164
+ \`producerPartnerId\`, \`transactionType\` and \`initiatedBy\` (the last two are carried on
165
+ TRANSACTION_PROCESSED, which merchants do not receive).`,
166
+ inputSchema: {
167
+ type: "object",
168
+ properties: {
169
+ transactionId: {
170
+ type: "string",
171
+ description: "Transaction ID to simulate callback for"
172
+ },
173
+ status: {
174
+ type: "string",
175
+ enum: ["CONFIRMED", "DECLINED", "FAILED", "EXPIRED", "CANCELLED"],
176
+ description: "Transaction status to simulate",
177
+ default: "CONFIRMED"
178
+ },
179
+ callbackUrl: {
180
+ type: "string",
181
+ description: "URL where callback would be sent (for simulation only)",
182
+ format: "uri"
183
+ },
184
+ amount: {
185
+ type: "string",
186
+ description: "Transaction amount (e.g., '57.60')",
187
+ default: "57.60"
188
+ },
189
+ currency: {
190
+ type: "string",
191
+ enum: ["EUR", "USD", "GBP"],
192
+ description: "Transaction currency",
193
+ default: "EUR"
194
+ },
195
+ fee: {
196
+ type: "string",
197
+ description: "The payware fee on this transaction, at the currency's smallest payable unit (e.g. '0.29'). Not always two decimals - it follows the currency. Pass null to model the anomaly case, where a transaction has no recorded fee and the server OMITS the property entirely; that is deliberately distinguishable from a genuine '0.00'."
198
+ },
199
+ feeFixed: {
200
+ type: "string",
201
+ description: "Fixed fee component of the applicable fee configuration, e.g. '0.1000'. Informational, for reconciliation. Present on FINALIZED callbacks since 2026-08-21 - before that a merchant got the total fee but not the breakdown that produced it. Pass null to model a transaction with no fee configuration, where it is omitted.",
202
+ default: "0.1000"
203
+ },
204
+ feeRate: {
205
+ type: "string",
206
+ description: "Variable rate of the applicable fee configuration, e.g. '0.0150' = 1.5%. Informational, same as feeFixed. Pass null to model a transaction with no fee configuration.",
207
+ default: "0.0150"
208
+ },
209
+ statusMessage: {
210
+ type: "string",
211
+ description: "Custom status message for the transaction callback"
212
+ },
213
+ paymentMethod: {
214
+ type: "string",
215
+ enum: ["A2A", "CARD_FUNDED", "BNPL", "INSTANT_CREDIT"],
216
+ description: "Payment method chosen by customer. A2A = direct transfer. CARD_FUNDED = card-linked account. BNPL = buy now pay later. INSTANT_CREDIT = credit line."
217
+ },
218
+ poiId: {
219
+ type: "string",
220
+ description: "Set to simulate a POI sale: the callback then carries the poiId of the POI that created the transaction"
221
+ }
222
+ },
223
+ required: ["transactionId"],
224
+ additionalProperties: false
225
+ },
226
+
227
+ async handler(args) {
228
+ const {
229
+ transactionId,
230
+ status = 'CONFIRMED',
231
+ callbackUrl,
232
+ amount = '57.60',
233
+ currency = 'EUR',
234
+ // No default: an omitted fee falls through to generateMockCallback's own derivation.
235
+ // Defaulting it to '0.00' here made every simulated callback assert a zero fee, which is the
236
+ // one value the real payload never sends for "no fee" - it omits the property instead.
237
+ fee,
238
+ feeFixed = '0.1000',
239
+ feeRate = '0.0150',
240
+ statusMessage,
241
+ paymentMethod,
242
+ poiId
243
+ } = args;
244
+
245
+ if (!transactionId) {
246
+ throw new Error("Transaction ID is required");
247
+ }
248
+
249
+ // Generate mock callback payload
250
+ const payload = generateMockCallback(transactionId, status, {
251
+ amount,
252
+ currency,
253
+ fee,
254
+ feeFixed,
255
+ feeRate,
256
+ statusMessage,
257
+ paymentMethod,
258
+ poiId
259
+ });
260
+
261
+ // Simulate callback delivery if URL provided
262
+ let deliveryResult = null;
263
+ if (callbackUrl) {
264
+ deliveryResult = await simulateCallbackDelivery(callbackUrl, payload);
265
+ }
266
+
267
+ const statusEmojis = {
268
+ 'CONFIRMED': '✅',
269
+ 'DECLINED': '❌',
270
+ 'FAILED': '💥',
271
+ 'EXPIRED': '⏰',
272
+ 'CANCELLED': '🚫'
273
+ };
274
+
275
+ const statusEmoji = statusEmojis[status] || '❓';
276
+
277
+ return {
278
+ content: [{
279
+ type: "text",
280
+ text: `🔄 **Callback Simulation Complete**
281
+
282
+ **Transaction:** ${transactionId}
283
+ **Simulated Status:** ${statusEmoji} ${status}
284
+
285
+ **Mock Callback Payload:**
286
+ \`\`\`json
287
+ ${JSON.stringify(payload, null, 2)}
288
+ \`\`\`
289
+
290
+ ${deliveryResult ? `
291
+ **Callback Delivery Simulation:**
292
+ ${deliveryResult.success ? '✅ Callback would be delivered successfully' : '❌ Callback delivery would fail'}
293
+
294
+ **Delivery Details:**
295
+ - URL: ${deliveryResult.url || 'N/A'}
296
+ - Headers: ${JSON.stringify(deliveryResult.headers || {}, null, 2)}
297
+ - Simulated At: ${deliveryResult.simulatedAt || 'N/A'}
298
+
299
+ **Note:** ${deliveryResult.note || 'No actual HTTP request was made'}
300
+ ` : '**No callback URL provided** - only payload generated'}
301
+
302
+ **Status Explanations:**
303
+ - ✅ **CONFIRMED**: Successfully finalized
304
+ - ❌ **DECLINED**: Declined by the user, processing or finalizing payment institutions
305
+ - 💥 **FAILED**: Failed due to technical reasons or other
306
+ - ⏰ **EXPIRED**: Time to live of the transaction has passed
307
+ - 🚫 **CANCELLED**: Transaction canceled by the originator
308
+
309
+ **Integration Testing:**
310
+ 1. Use this payload to test your callback handler
311
+ 2. Verify your system processes each status correctly
312
+ 3. Check error handling for DECLINED/FAILED statuses
313
+ 4. Ensure proper logging and monitoring
314
+
315
+ **Next Steps:**
316
+ 1. Implement callback endpoint to receive this payload
317
+ 2. Test with different status scenarios
318
+ 3. Add signature verification in production
319
+ 4. Set up proper error handling and retry logic`
320
+ }]
321
+ };
322
+ }
314
323
  };