payment-skill 1.0.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.
Files changed (110) hide show
  1. package/LICENSE +62 -0
  2. package/README.md +545 -0
  3. package/SKILL.md +99 -0
  4. package/SUPPORT.md +153 -0
  5. package/bin/payment-skill.js +2 -0
  6. package/dashboard.html +669 -0
  7. package/dist/api/bunq.d.ts +35 -0
  8. package/dist/api/bunq.d.ts.map +1 -0
  9. package/dist/api/bunq.js +164 -0
  10. package/dist/api/bunq.js.map +1 -0
  11. package/dist/api/wise.d.ts +32 -0
  12. package/dist/api/wise.d.ts.map +1 -0
  13. package/dist/api/wise.js +155 -0
  14. package/dist/api/wise.js.map +1 -0
  15. package/dist/cli.d.ts +8 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +69 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/commands/bunq.d.ts +8 -0
  20. package/dist/commands/bunq.d.ts.map +1 -0
  21. package/dist/commands/bunq.js +193 -0
  22. package/dist/commands/bunq.js.map +1 -0
  23. package/dist/commands/config.d.ts +8 -0
  24. package/dist/commands/config.d.ts.map +1 -0
  25. package/dist/commands/config.js +70 -0
  26. package/dist/commands/config.js.map +1 -0
  27. package/dist/commands/emergency.d.ts +8 -0
  28. package/dist/commands/emergency.d.ts.map +1 -0
  29. package/dist/commands/emergency.js +85 -0
  30. package/dist/commands/emergency.js.map +1 -0
  31. package/dist/commands/limits.d.ts +6 -0
  32. package/dist/commands/limits.d.ts.map +1 -0
  33. package/dist/commands/limits.js +125 -0
  34. package/dist/commands/limits.js.map +1 -0
  35. package/dist/commands/merchant.d.ts +6 -0
  36. package/dist/commands/merchant.d.ts.map +1 -0
  37. package/dist/commands/merchant.js +41 -0
  38. package/dist/commands/merchant.js.map +1 -0
  39. package/dist/commands/pay.d.ts +10 -0
  40. package/dist/commands/pay.d.ts.map +1 -0
  41. package/dist/commands/pay.js +112 -0
  42. package/dist/commands/pay.js.map +1 -0
  43. package/dist/commands/provider.d.ts +6 -0
  44. package/dist/commands/provider.d.ts.map +1 -0
  45. package/dist/commands/provider.js +74 -0
  46. package/dist/commands/provider.js.map +1 -0
  47. package/dist/commands/server.d.ts +8 -0
  48. package/dist/commands/server.d.ts.map +1 -0
  49. package/dist/commands/server.js +92 -0
  50. package/dist/commands/server.js.map +1 -0
  51. package/dist/commands/template.d.ts +8 -0
  52. package/dist/commands/template.d.ts.map +1 -0
  53. package/dist/commands/template.js +161 -0
  54. package/dist/commands/template.js.map +1 -0
  55. package/dist/commands/transaction.d.ts +6 -0
  56. package/dist/commands/transaction.d.ts.map +1 -0
  57. package/dist/commands/transaction.js +72 -0
  58. package/dist/commands/transaction.js.map +1 -0
  59. package/dist/commands/wise.d.ts +8 -0
  60. package/dist/commands/wise.d.ts.map +1 -0
  61. package/dist/commands/wise.js +240 -0
  62. package/dist/commands/wise.js.map +1 -0
  63. package/dist/core/config.d.ts +40 -0
  64. package/dist/core/config.d.ts.map +1 -0
  65. package/dist/core/config.js +201 -0
  66. package/dist/core/config.js.map +1 -0
  67. package/dist/core/template-engine.d.ts +27 -0
  68. package/dist/core/template-engine.d.ts.map +1 -0
  69. package/dist/core/template-engine.js +410 -0
  70. package/dist/core/template-engine.js.map +1 -0
  71. package/dist/core/transaction.d.ts +31 -0
  72. package/dist/core/transaction.d.ts.map +1 -0
  73. package/dist/core/transaction.js +214 -0
  74. package/dist/core/transaction.js.map +1 -0
  75. package/dist/index.d.ts +12 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +36 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/server/server.d.ts +14 -0
  80. package/dist/server/server.d.ts.map +1 -0
  81. package/dist/server/server.js +120 -0
  82. package/dist/server/server.js.map +1 -0
  83. package/dist/types/index.d.ts +141 -0
  84. package/dist/types/index.d.ts.map +1 -0
  85. package/dist/types/index.js +8 -0
  86. package/dist/types/index.js.map +1 -0
  87. package/logo.png +0 -0
  88. package/package.json +78 -0
  89. package/src/api/bunq.ts +257 -0
  90. package/src/api/wise.ts +204 -0
  91. package/src/cli.ts +67 -0
  92. package/src/commands/bunq.ts +223 -0
  93. package/src/commands/config.ts +72 -0
  94. package/src/commands/emergency.ts +94 -0
  95. package/src/commands/limits.ts +126 -0
  96. package/src/commands/merchant.ts +39 -0
  97. package/src/commands/pay.ts +109 -0
  98. package/src/commands/provider.ts +75 -0
  99. package/src/commands/server.ts +59 -0
  100. package/src/commands/template.ts +172 -0
  101. package/src/commands/transaction.ts +66 -0
  102. package/src/commands/wise.ts +279 -0
  103. package/src/core/config.ts +202 -0
  104. package/src/core/template-engine.ts +454 -0
  105. package/src/core/transaction.ts +228 -0
  106. package/src/index.ts +14 -0
  107. package/src/server/server.ts +131 -0
  108. package/src/types/index.ts +178 -0
  109. package/tsconfig.json +23 -0
  110. package/verified-merchants.json +63 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Payment Skill - Core Configuration Manager
3
+ *
4
+ * Manages application configuration, limits, and emergency stop state
5
+ */
6
+
7
+ import * as fs from 'fs-extra';
8
+ import * as path from 'path';
9
+ import {
10
+ ProviderConfig,
11
+ LimitConfig,
12
+ TimeWindowConfig,
13
+ EmergencyStopState,
14
+ WiseConfig,
15
+ BunqConfig
16
+ } from '../types';
17
+
18
+ const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.payment-skill');
19
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
20
+ const EMERGENCY_FILE = path.join(CONFIG_DIR, 'emergency.json');
21
+
22
+ export class ConfigManager {
23
+ private config: any = {};
24
+ private emergencyState: EmergencyStopState = { active: false, pendingTransactions: [] };
25
+
26
+ constructor() {
27
+ this.ensureConfigDir();
28
+ this.loadConfig();
29
+ this.loadEmergencyState();
30
+ }
31
+
32
+ private ensureConfigDir(): void {
33
+ if (!fs.existsSync(CONFIG_DIR)) {
34
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
35
+ }
36
+ }
37
+
38
+ private loadConfig(): void {
39
+ if (fs.existsSync(CONFIG_FILE)) {
40
+ this.config = fs.readJsonSync(CONFIG_FILE);
41
+ } else {
42
+ this.config = this.getDefaultConfig();
43
+ this.saveConfig();
44
+ }
45
+ }
46
+
47
+ private loadEmergencyState(): void {
48
+ if (fs.existsSync(EMERGENCY_FILE)) {
49
+ this.emergencyState = fs.readJsonSync(EMERGENCY_FILE);
50
+ }
51
+ }
52
+
53
+ private saveConfig(): void {
54
+ fs.writeJsonSync(CONFIG_FILE, this.config, { spaces: 2 });
55
+ }
56
+
57
+ private saveEmergencyState(): void {
58
+ fs.writeJsonSync(EMERGENCY_FILE, this.emergencyState, { spaces: 2 });
59
+ }
60
+
61
+ private getDefaultConfig(): any {
62
+ return {
63
+ version: '1.0.0',
64
+ providers: {},
65
+ limits: {
66
+ perTransaction: 10000,
67
+ daily: 50000,
68
+ weekly: 200000,
69
+ monthly: 500000,
70
+ maxTransactionsPerHour: 10
71
+ },
72
+ timeWindow: {
73
+ enabled: false,
74
+ start: '08:00',
75
+ end: '22:00',
76
+ timezone: 'Europe/Bucharest'
77
+ },
78
+ blockedCategories: ['gambling', 'adult', 'drugs', 'weapons', 'tobacco'],
79
+ verifiedMerchantsOnly: true,
80
+ webhookUrl: null
81
+ };
82
+ }
83
+
84
+ // Provider Management
85
+ setProvider(name: string, config: ProviderConfig): void {
86
+ this.config.providers[name] = config;
87
+ this.saveConfig();
88
+ }
89
+
90
+ getProvider(name: string): ProviderConfig | null {
91
+ return this.config.providers[name] || null;
92
+ }
93
+
94
+ getAllProviders(): Record<string, ProviderConfig> {
95
+ return this.config.providers;
96
+ }
97
+
98
+ removeProvider(name: string): void {
99
+ delete this.config.providers[name];
100
+ this.saveConfig();
101
+ }
102
+
103
+ // Limits Management
104
+ getLimits(): LimitConfig {
105
+ return this.config.limits;
106
+ }
107
+
108
+ setLimits(limits: Partial<LimitConfig>): void {
109
+ this.config.limits = { ...this.config.limits, ...limits };
110
+ this.saveConfig();
111
+ }
112
+
113
+ // Time Window Management
114
+ getTimeWindow(): TimeWindowConfig {
115
+ return this.config.timeWindow;
116
+ }
117
+
118
+ setTimeWindow(config: Partial<TimeWindowConfig>): void {
119
+ this.config.timeWindow = { ...this.config.timeWindow, ...config };
120
+ this.saveConfig();
121
+ }
122
+
123
+ // Emergency Stop
124
+ activateEmergencyStop(reason?: string): void {
125
+ this.emergencyState = {
126
+ active: true,
127
+ activatedAt: new Date(),
128
+ reason: reason || 'Manual activation',
129
+ pendingTransactions: this.emergencyState.pendingTransactions
130
+ };
131
+ this.saveEmergencyState();
132
+ }
133
+
134
+ deactivateEmergencyStop(): void {
135
+ this.emergencyState = {
136
+ active: false,
137
+ pendingTransactions: []
138
+ };
139
+ this.saveEmergencyState();
140
+ }
141
+
142
+ isEmergencyStopActive(): boolean {
143
+ return this.emergencyState.active;
144
+ }
145
+
146
+ getEmergencyStopState(): EmergencyStopState {
147
+ return this.emergencyState;
148
+ }
149
+
150
+ addPendingTransaction(transactionId: string): void {
151
+ if (!this.emergencyState.pendingTransactions.includes(transactionId)) {
152
+ this.emergencyState.pendingTransactions.push(transactionId);
153
+ this.saveEmergencyState();
154
+ }
155
+ }
156
+
157
+ removePendingTransaction(transactionId: string): void {
158
+ this.emergencyState.pendingTransactions =
159
+ this.emergencyState.pendingTransactions.filter(id => id !== transactionId);
160
+ this.saveEmergencyState();
161
+ }
162
+
163
+ // General Config
164
+ getConfig(): any {
165
+ return this.config;
166
+ }
167
+
168
+ setConfig(key: string, value: any): void {
169
+ this.config[key] = value;
170
+ this.saveConfig();
171
+ }
172
+
173
+ // Blocked Categories
174
+ getBlockedCategories(): string[] {
175
+ return this.config.blockedCategories;
176
+ }
177
+
178
+ addBlockedCategory(category: string): void {
179
+ if (!this.config.blockedCategories.includes(category)) {
180
+ this.config.blockedCategories.push(category);
181
+ this.saveConfig();
182
+ }
183
+ }
184
+
185
+ removeBlockedCategory(category: string): void {
186
+ this.config.blockedCategories =
187
+ this.config.blockedCategories.filter((c: string) => c !== category);
188
+ this.saveConfig();
189
+ }
190
+
191
+ // Webhook URL
192
+ getWebhookUrl(): string | null {
193
+ return this.config.webhookUrl;
194
+ }
195
+
196
+ setWebhookUrl(url: string): void {
197
+ this.config.webhookUrl = url;
198
+ this.saveConfig();
199
+ }
200
+ }
201
+
202
+ export const configManager = new ConfigManager();
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Payment Skill - Template Engine
3
+ *
4
+ * Implements the configurable template-based payment flow system
5
+ * OpenClaw selects templates and provides parameters
6
+ * Payment-skill executes the predefined flow
7
+ */
8
+
9
+ import * as fs from 'fs-extra';
10
+ import * as path from 'path';
11
+ import { CommandTemplate, TemplateStep, Transaction } from '../types';
12
+ import { WiseClient } from '../api/wise';
13
+ import { BunqClient } from '../api/bunq';
14
+ import { configManager } from './config';
15
+ import { transactionManager } from './transaction';
16
+
17
+ const TEMPLATES_DIR = path.join(__dirname, '../../templates');
18
+
19
+ export class TemplateEngine {
20
+ private templates: Map<string, CommandTemplate> = new Map();
21
+
22
+ constructor() {
23
+ this.loadTemplates();
24
+ }
25
+
26
+ private loadTemplates(): void {
27
+ // Ensure templates directory exists
28
+ if (!fs.existsSync(TEMPLATES_DIR)) {
29
+ fs.mkdirSync(TEMPLATES_DIR, { recursive: true });
30
+ this.createDefaultTemplates();
31
+ }
32
+
33
+ // Load all template files
34
+ const files = fs.readdirSync(TEMPLATES_DIR).filter(f => f.endsWith('.json'));
35
+
36
+ for (const file of files) {
37
+ const template = fs.readJsonSync(path.join(TEMPLATES_DIR, file));
38
+ this.templates.set(template.templateId, template);
39
+ }
40
+ }
41
+
42
+ private createDefaultTemplates(): void {
43
+ const defaultTemplates: CommandTemplate[] = [
44
+ {
45
+ templateId: 'wise_standard_transfer',
46
+ merchant: 'wise.com',
47
+ version: '1.0.0',
48
+ description: 'Standard Wise transfer flow with PSD2 confirmation',
49
+ prerequisites: {
50
+ apiKey: 'required',
51
+ webhookEndpoint: 'recommended'
52
+ },
53
+ steps: [
54
+ {
55
+ order: 1,
56
+ name: 'create_quote',
57
+ command: 'wise.createQuote',
58
+ params: {
59
+ profileId: '{{profileId}}',
60
+ sourceCurrency: '{{sourceCurrency}}',
61
+ targetCurrency: '{{targetCurrency}}',
62
+ sourceAmount: '{{amount}}'
63
+ },
64
+ output: {
65
+ quoteId: 'id',
66
+ rate: 'rate',
67
+ fee: 'fee'
68
+ }
69
+ },
70
+ {
71
+ order: 2,
72
+ name: 'create_transfer',
73
+ command: 'wise.createTransfer',
74
+ params: {
75
+ profileId: '{{profileId}}',
76
+ quoteId: '{{quoteId}}',
77
+ targetAccountId: '{{recipientId}}',
78
+ reference: '{{reference}}'
79
+ },
80
+ output: {
81
+ transferId: 'id',
82
+ status: 'status'
83
+ },
84
+ async: true
85
+ },
86
+ {
87
+ order: 3,
88
+ name: 'fund_transfer',
89
+ command: 'wise.fundTransfer',
90
+ params: {
91
+ profileId: '{{profileId}}',
92
+ transferId: '{{transferId}}'
93
+ },
94
+ async: true,
95
+ confirmation: {
96
+ type: 'webhook',
97
+ events: ['transfer.completed', 'transfer.failed'],
98
+ timeout: 300
99
+ }
100
+ }
101
+ ],
102
+ errorHandling: {
103
+ retryOn: ['network_error', 'rate_limit'],
104
+ maxRetries: 3,
105
+ fallback: 'cancel_transfer'
106
+ }
107
+ },
108
+ {
109
+ templateId: 'bunq_instant_payment',
110
+ merchant: 'bunq.com',
111
+ version: '1.0.0',
112
+ description: 'Instant Bunq payment to IBAN',
113
+ prerequisites: {
114
+ apiKey: 'required',
115
+ webhookEndpoint: 'optional'
116
+ },
117
+ steps: [
118
+ {
119
+ order: 1,
120
+ name: 'create_payment',
121
+ command: 'bunq.createPayment',
122
+ params: {
123
+ userId: '{{userId}}',
124
+ accountId: '{{accountId}}',
125
+ amount: '{{amount}}',
126
+ currency: '{{currency}}',
127
+ counterpartyIban: '{{recipientIban}}',
128
+ counterpartyName: '{{recipientName}}',
129
+ description: '{{description}}'
130
+ },
131
+ output: {
132
+ paymentId: 'id',
133
+ status: 'status'
134
+ }
135
+ }
136
+ ],
137
+ errorHandling: {
138
+ retryOn: ['network_error'],
139
+ maxRetries: 2
140
+ }
141
+ },
142
+ {
143
+ templateId: 'bunq_payment_request',
144
+ merchant: 'bunq.com',
145
+ version: '1.0.0',
146
+ description: 'Request payment from someone via Bunq',
147
+ prerequisites: {
148
+ apiKey: 'required'
149
+ },
150
+ steps: [
151
+ {
152
+ order: 1,
153
+ name: 'create_request',
154
+ command: 'bunq.createRequestInquiry',
155
+ params: {
156
+ userId: '{{userId}}',
157
+ accountId: '{{accountId}}',
158
+ amount: '{{amount}}',
159
+ currency: '{{currency}}',
160
+ counterpartyAlias: {
161
+ type: '{{recipientType}}',
162
+ value: '{{recipientValue}}'
163
+ },
164
+ description: '{{description}}'
165
+ },
166
+ output: {
167
+ requestId: 'id',
168
+ status: 'status'
169
+ },
170
+ async: true,
171
+ confirmation: {
172
+ type: 'poll',
173
+ timeout: 86400,
174
+ pollInterval: 60
175
+ }
176
+ }
177
+ ],
178
+ errorHandling: {
179
+ retryOn: ['network_error'],
180
+ maxRetries: 3
181
+ }
182
+ },
183
+ {
184
+ templateId: 'stripe_connect_charge',
185
+ merchant: 'stripe.com',
186
+ version: '1.0.0',
187
+ description: 'Create charge through Stripe Connect',
188
+ prerequisites: {
189
+ apiKey: 'required',
190
+ webhookEndpoint: 'required'
191
+ },
192
+ steps: [
193
+ {
194
+ order: 1,
195
+ name: 'create_payment_intent',
196
+ command: 'stripe.paymentIntents.create',
197
+ params: {
198
+ amount: '{{amount}}',
199
+ currency: '{{currency}}',
200
+ customer: '{{customerId}}',
201
+ automatic_payment_methods: { enabled: true }
202
+ },
203
+ output: {
204
+ paymentIntentId: 'id',
205
+ clientSecret: 'client_secret',
206
+ status: 'status'
207
+ },
208
+ async: true
209
+ },
210
+ {
211
+ order: 2,
212
+ name: 'confirm_payment',
213
+ command: 'stripe.paymentIntents.confirm',
214
+ params: {
215
+ paymentIntentId: '{{paymentIntentId}}'
216
+ },
217
+ condition: 'requires_confirmation',
218
+ async: true,
219
+ confirmation: {
220
+ type: 'webhook',
221
+ events: ['payment_intent.succeeded', 'payment_intent.payment_failed'],
222
+ timeout: 600
223
+ }
224
+ }
225
+ ],
226
+ errorHandling: {
227
+ retryOn: ['network_error', 'idempotency_error'],
228
+ maxRetries: 3
229
+ }
230
+ }
231
+ ];
232
+
233
+ for (const template of defaultTemplates) {
234
+ const filePath = path.join(TEMPLATES_DIR, `${template.templateId}.json`);
235
+ fs.writeJsonSync(filePath, template, { spaces: 2 });
236
+ this.templates.set(template.templateId, template);
237
+ }
238
+ }
239
+
240
+ getTemplate(templateId: string): CommandTemplate | null {
241
+ return this.templates.get(templateId) || null;
242
+ }
243
+
244
+ getAllTemplates(): CommandTemplate[] {
245
+ return Array.from(this.templates.values());
246
+ }
247
+
248
+ getTemplatesForMerchant(merchant: string): CommandTemplate[] {
249
+ return this.getAllTemplates().filter(t => t.merchant === merchant);
250
+ }
251
+
252
+ async executeTemplate(
253
+ templateId: string,
254
+ params: Record<string, any>
255
+ ): Promise<Transaction> {
256
+ const template = this.getTemplate(templateId);
257
+ if (!template) {
258
+ throw new Error(`Template '${templateId}' not found`);
259
+ }
260
+
261
+ // Check emergency stop
262
+ if (configManager.isEmergencyStopActive()) {
263
+ throw new Error('Emergency stop is active. Cannot execute template.');
264
+ }
265
+
266
+ // Create transaction record
267
+ const tx = transactionManager.createTransaction(
268
+ template.merchant,
269
+ templateId,
270
+ parseFloat(params.amount) || 0,
271
+ params.currency || 'EUR',
272
+ { templateId, params }
273
+ );
274
+
275
+ try {
276
+ // Execute each step
277
+ const context: Record<string, any> = { ...params };
278
+
279
+ for (const step of template.steps.sort((a, b) => a.order - b.order)) {
280
+ await this.executeStep(step, context, tx);
281
+ }
282
+
283
+ transactionManager.updateTransactionStatus(tx.id, 'completed');
284
+ return tx;
285
+ } catch (error: any) {
286
+ transactionManager.updateTransactionStatus(tx.id, 'failed', error.message);
287
+ throw error;
288
+ }
289
+ }
290
+
291
+ private async executeStep(
292
+ step: TemplateStep,
293
+ context: Record<string, any>,
294
+ tx: Transaction
295
+ ): Promise<void> {
296
+ // Replace template variables with actual values
297
+ const resolvedParams = this.resolveParams(step.params, context);
298
+
299
+ // Execute the command
300
+ const result = await this.executeCommand(step.command, resolvedParams);
301
+
302
+ // Store outputs in context for subsequent steps
303
+ if (step.output && result) {
304
+ for (const [key, path] of Object.entries(step.output)) {
305
+ context[key] = this.getNestedValue(result, path);
306
+ }
307
+ }
308
+
309
+ // Handle async steps
310
+ if (step.async && step.confirmation) {
311
+ await this.waitForConfirmation(step.confirmation, context, tx);
312
+ }
313
+ }
314
+
315
+ private resolveParams(params: any, context: Record<string, any>): any {
316
+ if (typeof params === 'string') {
317
+ // Replace {{variable}} with context value
318
+ return params.replace(/\{\{(\w+)\}\}/g, (match, key) => {
319
+ return context[key] !== undefined ? context[key] : match;
320
+ });
321
+ }
322
+
323
+ if (Array.isArray(params)) {
324
+ return params.map(p => this.resolveParams(p, context));
325
+ }
326
+
327
+ if (typeof params === 'object' && params !== null) {
328
+ const resolved: any = {};
329
+ for (const [key, value] of Object.entries(params)) {
330
+ resolved[key] = this.resolveParams(value, context);
331
+ }
332
+ return resolved;
333
+ }
334
+
335
+ return params;
336
+ }
337
+
338
+ private getNestedValue(obj: any, path: string): any {
339
+ return path.split('.').reduce((o, p) => o?.[p], obj);
340
+ }
341
+
342
+ private async executeCommand(command: string, params: any): Promise<any> {
343
+ const [provider, method] = command.split('.');
344
+
345
+ switch (provider) {
346
+ case 'wise':
347
+ return this.executeWiseCommand(method, params);
348
+ case 'bunq':
349
+ return this.executeBunqCommand(method, params);
350
+ case 'stripe':
351
+ // Stripe implementation would go here
352
+ throw new Error('Stripe commands not yet implemented');
353
+ default:
354
+ throw new Error(`Unknown provider: ${provider}`);
355
+ }
356
+ }
357
+
358
+ private async executeWiseCommand(method: string, params: any): Promise<any> {
359
+ const config = configManager.getProvider('wise');
360
+ if (!config) {
361
+ throw new Error('Wise not configured');
362
+ }
363
+
364
+ const client = new WiseClient(config as any);
365
+
366
+ switch (method) {
367
+ case 'createQuote':
368
+ return client.createQuote(
369
+ params.profileId,
370
+ params.sourceCurrency,
371
+ params.targetCurrency,
372
+ parseFloat(params.sourceAmount)
373
+ );
374
+ case 'createTransfer':
375
+ return client.createTransfer(
376
+ params.profileId,
377
+ params.quoteId,
378
+ params.targetAccountId,
379
+ params.reference
380
+ );
381
+ case 'fundTransfer':
382
+ return client.fundTransfer(params.profileId, params.transferId);
383
+ default:
384
+ throw new Error(`Unknown Wise command: ${method}`);
385
+ }
386
+ }
387
+
388
+ private async executeBunqCommand(method: string, params: any): Promise<any> {
389
+ const config = configManager.getProvider('bunq');
390
+ if (!config) {
391
+ throw new Error('Bunq not configured');
392
+ }
393
+
394
+ const client = new BunqClient(config as any);
395
+
396
+ switch (method) {
397
+ case 'createPayment':
398
+ return client.createPayment(
399
+ params.userId,
400
+ params.accountId,
401
+ params.amount,
402
+ params.currency,
403
+ params.counterpartyIban,
404
+ params.counterpartyName,
405
+ params.description
406
+ );
407
+ case 'createRequestInquiry':
408
+ return client.createRequestInquiry(
409
+ params.userId,
410
+ params.accountId,
411
+ params.amount,
412
+ params.currency,
413
+ params.counterpartyAlias,
414
+ params.description
415
+ );
416
+ default:
417
+ throw new Error(`Unknown Bunq command: ${method}`);
418
+ }
419
+ }
420
+
421
+ private async waitForConfirmation(
422
+ confirmation: any,
423
+ context: Record<string, any>,
424
+ tx: Transaction
425
+ ): Promise<void> {
426
+ const { type, timeout, events, pollInterval } = confirmation;
427
+
428
+ if (type === 'webhook') {
429
+ // Webhook confirmation - wait for webhook handler to update
430
+ console.log(`Waiting for webhook confirmation (${timeout}s)...`);
431
+ // In real implementation, this would set up a promise that resolves on webhook
432
+ await new Promise(resolve => setTimeout(resolve, 5000));
433
+ } else if (type === 'poll') {
434
+ // Polling confirmation
435
+ const startTime = Date.now();
436
+ const timeoutMs = (timeout || 300) * 1000;
437
+
438
+ while (Date.now() - startTime < timeoutMs) {
439
+ // Poll for status
440
+ await new Promise(resolve => setTimeout(resolve, (pollInterval || 5) * 1000));
441
+
442
+ // Check if confirmed
443
+ const updatedTx = transactionManager.getTransaction(tx.id);
444
+ if (updatedTx?.status === 'completed') {
445
+ return;
446
+ }
447
+ }
448
+
449
+ throw new Error('Confirmation timeout');
450
+ }
451
+ }
452
+ }
453
+
454
+ export const templateEngine = new TemplateEngine();