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.
- package/LICENSE +62 -0
- package/README.md +545 -0
- package/SKILL.md +99 -0
- package/SUPPORT.md +153 -0
- package/bin/payment-skill.js +2 -0
- package/dashboard.html +669 -0
- package/dist/api/bunq.d.ts +35 -0
- package/dist/api/bunq.d.ts.map +1 -0
- package/dist/api/bunq.js +164 -0
- package/dist/api/bunq.js.map +1 -0
- package/dist/api/wise.d.ts +32 -0
- package/dist/api/wise.d.ts.map +1 -0
- package/dist/api/wise.js +155 -0
- package/dist/api/wise.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +69 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/bunq.d.ts +8 -0
- package/dist/commands/bunq.d.ts.map +1 -0
- package/dist/commands/bunq.js +193 -0
- package/dist/commands/bunq.js.map +1 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +70 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/emergency.d.ts +8 -0
- package/dist/commands/emergency.d.ts.map +1 -0
- package/dist/commands/emergency.js +85 -0
- package/dist/commands/emergency.js.map +1 -0
- package/dist/commands/limits.d.ts +6 -0
- package/dist/commands/limits.d.ts.map +1 -0
- package/dist/commands/limits.js +125 -0
- package/dist/commands/limits.js.map +1 -0
- package/dist/commands/merchant.d.ts +6 -0
- package/dist/commands/merchant.d.ts.map +1 -0
- package/dist/commands/merchant.js +41 -0
- package/dist/commands/merchant.js.map +1 -0
- package/dist/commands/pay.d.ts +10 -0
- package/dist/commands/pay.d.ts.map +1 -0
- package/dist/commands/pay.js +112 -0
- package/dist/commands/pay.js.map +1 -0
- package/dist/commands/provider.d.ts +6 -0
- package/dist/commands/provider.d.ts.map +1 -0
- package/dist/commands/provider.js +74 -0
- package/dist/commands/provider.js.map +1 -0
- package/dist/commands/server.d.ts +8 -0
- package/dist/commands/server.d.ts.map +1 -0
- package/dist/commands/server.js +92 -0
- package/dist/commands/server.js.map +1 -0
- package/dist/commands/template.d.ts +8 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +161 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/transaction.d.ts +6 -0
- package/dist/commands/transaction.d.ts.map +1 -0
- package/dist/commands/transaction.js +72 -0
- package/dist/commands/transaction.js.map +1 -0
- package/dist/commands/wise.d.ts +8 -0
- package/dist/commands/wise.d.ts.map +1 -0
- package/dist/commands/wise.js +240 -0
- package/dist/commands/wise.js.map +1 -0
- package/dist/core/config.d.ts +40 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +201 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/template-engine.d.ts +27 -0
- package/dist/core/template-engine.d.ts.map +1 -0
- package/dist/core/template-engine.js +410 -0
- package/dist/core/template-engine.js.map +1 -0
- package/dist/core/transaction.d.ts +31 -0
- package/dist/core/transaction.d.ts.map +1 -0
- package/dist/core/transaction.js +214 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/server/server.d.ts +14 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +120 -0
- package/dist/server/server.js.map +1 -0
- package/dist/types/index.d.ts +141 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/logo.png +0 -0
- package/package.json +78 -0
- package/src/api/bunq.ts +257 -0
- package/src/api/wise.ts +204 -0
- package/src/cli.ts +67 -0
- package/src/commands/bunq.ts +223 -0
- package/src/commands/config.ts +72 -0
- package/src/commands/emergency.ts +94 -0
- package/src/commands/limits.ts +126 -0
- package/src/commands/merchant.ts +39 -0
- package/src/commands/pay.ts +109 -0
- package/src/commands/provider.ts +75 -0
- package/src/commands/server.ts +59 -0
- package/src/commands/template.ts +172 -0
- package/src/commands/transaction.ts +66 -0
- package/src/commands/wise.ts +279 -0
- package/src/core/config.ts +202 -0
- package/src/core/template-engine.ts +454 -0
- package/src/core/transaction.ts +228 -0
- package/src/index.ts +14 -0
- package/src/server/server.ts +131 -0
- package/src/types/index.ts +178 -0
- package/tsconfig.json +23 -0
- package/verified-merchants.json +63 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Skill - Transaction Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages all transactions, their states, and lifecycle
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs-extra';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { Transaction, TransactionStatus } from '../types';
|
|
10
|
+
import { configManager } from './config';
|
|
11
|
+
|
|
12
|
+
const DATA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.payment-skill');
|
|
13
|
+
const TRANSACTIONS_FILE = path.join(DATA_DIR, 'transactions.json');
|
|
14
|
+
|
|
15
|
+
export class TransactionManager {
|
|
16
|
+
private transactions: Map<string, Transaction> = new Map();
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.loadTransactions();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private loadTransactions(): void {
|
|
23
|
+
if (fs.existsSync(TRANSACTIONS_FILE)) {
|
|
24
|
+
const data = fs.readJsonSync(TRANSACTIONS_FILE);
|
|
25
|
+
Object.entries(data).forEach(([id, tx]: [string, any]) => {
|
|
26
|
+
this.transactions.set(id, {
|
|
27
|
+
...tx,
|
|
28
|
+
createdAt: new Date(tx.createdAt),
|
|
29
|
+
updatedAt: new Date(tx.updatedAt)
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private saveTransactions(): void {
|
|
36
|
+
const data: Record<string, Transaction> = {};
|
|
37
|
+
this.transactions.forEach((tx, id) => {
|
|
38
|
+
data[id] = tx;
|
|
39
|
+
});
|
|
40
|
+
fs.writeJsonSync(TRANSACTIONS_FILE, data, { spaces: 2 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
createTransaction(
|
|
44
|
+
provider: string,
|
|
45
|
+
merchant: string,
|
|
46
|
+
amount: number,
|
|
47
|
+
currency: string,
|
|
48
|
+
metadata?: Record<string, any>
|
|
49
|
+
): Transaction {
|
|
50
|
+
// Check emergency stop
|
|
51
|
+
if (configManager.isEmergencyStopActive()) {
|
|
52
|
+
throw new Error('Emergency stop is active. Cannot create new transactions.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check limits
|
|
56
|
+
this.checkLimits(amount, currency);
|
|
57
|
+
|
|
58
|
+
const transaction: Transaction = {
|
|
59
|
+
id: this.generateTransactionId(),
|
|
60
|
+
provider,
|
|
61
|
+
merchant,
|
|
62
|
+
amount,
|
|
63
|
+
currency: currency.toUpperCase(),
|
|
64
|
+
status: 'pending',
|
|
65
|
+
createdAt: new Date(),
|
|
66
|
+
updatedAt: new Date(),
|
|
67
|
+
metadata
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this.transactions.set(transaction.id, transaction);
|
|
71
|
+
this.saveTransactions();
|
|
72
|
+
|
|
73
|
+
return transaction;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private generateTransactionId(): string {
|
|
77
|
+
return `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private checkLimits(amount: number, currency: string): void {
|
|
81
|
+
const limits = configManager.getLimits();
|
|
82
|
+
|
|
83
|
+
// Check per-transaction limit
|
|
84
|
+
if (amount > limits.perTransaction) {
|
|
85
|
+
throw new Error(`Amount ${amount} exceeds per-transaction limit of ${limits.perTransaction}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check daily limit
|
|
89
|
+
const today = new Date().toDateString();
|
|
90
|
+
const dailyTotal = this.getDailyTotal(today);
|
|
91
|
+
if (dailyTotal + amount > limits.daily) {
|
|
92
|
+
throw new Error(`Daily limit of ${limits.daily} would be exceeded`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check hourly transaction count
|
|
96
|
+
const hourlyCount = this.getHourlyTransactionCount();
|
|
97
|
+
if (hourlyCount >= limits.maxTransactionsPerHour) {
|
|
98
|
+
throw new Error(`Hourly transaction limit of ${limits.maxTransactionsPerHour} reached`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check time window
|
|
102
|
+
const timeWindow = configManager.getTimeWindow();
|
|
103
|
+
if (timeWindow.enabled) {
|
|
104
|
+
const now = new Date();
|
|
105
|
+
const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
|
|
106
|
+
|
|
107
|
+
if (currentTime < timeWindow.start || currentTime > timeWindow.end) {
|
|
108
|
+
throw new Error(`Transactions not allowed outside time window ${timeWindow.start}-${timeWindow.end}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private getDailyTotal(date: string): number {
|
|
114
|
+
let total = 0;
|
|
115
|
+
this.transactions.forEach(tx => {
|
|
116
|
+
if (tx.createdAt.toDateString() === date &&
|
|
117
|
+
(tx.status === 'completed' || tx.status === 'processing')) {
|
|
118
|
+
total += tx.amount;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return total;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private getHourlyTransactionCount(): number {
|
|
125
|
+
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
|
126
|
+
let count = 0;
|
|
127
|
+
this.transactions.forEach(tx => {
|
|
128
|
+
if (tx.createdAt > oneHourAgo &&
|
|
129
|
+
(tx.status === 'pending' || tx.status === 'processing' || tx.status === 'completed')) {
|
|
130
|
+
count++;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return count;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
updateTransactionStatus(
|
|
137
|
+
transactionId: string,
|
|
138
|
+
status: TransactionStatus,
|
|
139
|
+
error?: string
|
|
140
|
+
): Transaction {
|
|
141
|
+
const tx = this.transactions.get(transactionId);
|
|
142
|
+
if (!tx) {
|
|
143
|
+
throw new Error(`Transaction ${transactionId} not found`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// If emergency stop is active, only allow cancellation
|
|
147
|
+
if (configManager.isEmergencyStopActive() && status !== 'cancelled') {
|
|
148
|
+
throw new Error('Emergency stop is active. Only cancellation allowed.');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
tx.status = status;
|
|
152
|
+
tx.updatedAt = new Date();
|
|
153
|
+
if (error) {
|
|
154
|
+
tx.error = error;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.transactions.set(transactionId, tx);
|
|
158
|
+
this.saveTransactions();
|
|
159
|
+
|
|
160
|
+
// Update emergency stop pending list
|
|
161
|
+
if (status === 'processing') {
|
|
162
|
+
configManager.addPendingTransaction(transactionId);
|
|
163
|
+
} else if (['completed', 'failed', 'cancelled'].includes(status)) {
|
|
164
|
+
configManager.removePendingTransaction(transactionId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return tx;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getTransaction(transactionId: string): Transaction | null {
|
|
171
|
+
return this.transactions.get(transactionId) || null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getTransactions(
|
|
175
|
+
filters?: {
|
|
176
|
+
status?: TransactionStatus;
|
|
177
|
+
provider?: string;
|
|
178
|
+
merchant?: string;
|
|
179
|
+
from?: Date;
|
|
180
|
+
to?: Date;
|
|
181
|
+
}
|
|
182
|
+
): Transaction[] {
|
|
183
|
+
let txs = Array.from(this.transactions.values());
|
|
184
|
+
|
|
185
|
+
if (filters?.status) {
|
|
186
|
+
txs = txs.filter(tx => tx.status === filters.status);
|
|
187
|
+
}
|
|
188
|
+
if (filters?.provider) {
|
|
189
|
+
txs = txs.filter(tx => tx.provider === filters.provider);
|
|
190
|
+
}
|
|
191
|
+
if (filters?.merchant) {
|
|
192
|
+
txs = txs.filter(tx => tx.merchant === filters.merchant);
|
|
193
|
+
}
|
|
194
|
+
if (filters?.from) {
|
|
195
|
+
txs = txs.filter(tx => tx.createdAt >= filters.from!);
|
|
196
|
+
}
|
|
197
|
+
if (filters?.to) {
|
|
198
|
+
txs = txs.filter(tx => tx.createdAt <= filters.to!);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return txs.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
getPendingTransactions(): Transaction[] {
|
|
205
|
+
return this.getTransactions({ status: 'pending' });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
cancelAllPending(): number {
|
|
209
|
+
let count = 0;
|
|
210
|
+
this.transactions.forEach((tx, id) => {
|
|
211
|
+
if (tx.status === 'pending' || tx.status === 'processing') {
|
|
212
|
+
this.updateTransactionStatus(id, 'cancelled', 'Cancelled by emergency stop');
|
|
213
|
+
count++;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return count;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
deleteTransaction(transactionId: string): boolean {
|
|
220
|
+
const deleted = this.transactions.delete(transactionId);
|
|
221
|
+
if (deleted) {
|
|
222
|
+
this.saveTransactions();
|
|
223
|
+
}
|
|
224
|
+
return deleted;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export const transactionManager = new TransactionManager();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Skill - Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Exports all modules for programmatic use
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { WiseClient } from './api/wise';
|
|
8
|
+
export { BunqClient } from './api/bunq';
|
|
9
|
+
export { ConfigManager, configManager } from './core/config';
|
|
10
|
+
export { TransactionManager, transactionManager } from './core/transaction';
|
|
11
|
+
export * from './types';
|
|
12
|
+
|
|
13
|
+
// Version
|
|
14
|
+
export const VERSION = '1.0.0';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Skill - Express Server
|
|
3
|
+
*
|
|
4
|
+
* Serves the web dashboard and handles webhooks
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import cors from 'cors';
|
|
9
|
+
import helmet from 'helmet';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { configManager } from '../core/config';
|
|
12
|
+
import { transactionManager } from '../core/transaction';
|
|
13
|
+
|
|
14
|
+
export class PaymentSkillServer {
|
|
15
|
+
private app: express.Application;
|
|
16
|
+
private port: number;
|
|
17
|
+
|
|
18
|
+
constructor(port: number = 8080) {
|
|
19
|
+
this.app = express();
|
|
20
|
+
this.port = port;
|
|
21
|
+
this.setupMiddleware();
|
|
22
|
+
this.setupRoutes();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private setupMiddleware(): void {
|
|
26
|
+
this.app.use(helmet({
|
|
27
|
+
contentSecurityPolicy: false // Allow inline scripts for dashboard
|
|
28
|
+
}));
|
|
29
|
+
this.app.use(cors());
|
|
30
|
+
this.app.use(express.json());
|
|
31
|
+
this.app.use(express.static(path.join(__dirname, '../../')));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private setupRoutes(): void {
|
|
35
|
+
// Health check
|
|
36
|
+
this.app.get('/api/health', (req, res) => {
|
|
37
|
+
res.json({
|
|
38
|
+
status: 'ok',
|
|
39
|
+
version: '1.0.0',
|
|
40
|
+
emergencyStop: configManager.isEmergencyStopActive()
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Get configuration
|
|
45
|
+
this.app.get('/api/config', (req, res) => {
|
|
46
|
+
const config = configManager.getConfig();
|
|
47
|
+
// Remove sensitive data
|
|
48
|
+
const safeConfig = {
|
|
49
|
+
...config,
|
|
50
|
+
providers: Object.entries(config.providers).reduce((acc, [key, val]: [string, any]) => {
|
|
51
|
+
acc[key] = {
|
|
52
|
+
name: val.name,
|
|
53
|
+
environment: val.environment,
|
|
54
|
+
// Don't expose API keys
|
|
55
|
+
apiKey: val.apiKey ? '***' : undefined
|
|
56
|
+
};
|
|
57
|
+
return acc;
|
|
58
|
+
}, {} as any)
|
|
59
|
+
};
|
|
60
|
+
res.json(safeConfig);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Get transactions
|
|
64
|
+
this.app.get('/api/transactions', (req, res) => {
|
|
65
|
+
const transactions = transactionManager.getTransactions();
|
|
66
|
+
res.json(transactions);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Get emergency stop status
|
|
70
|
+
this.app.get('/api/emergency', (req, res) => {
|
|
71
|
+
res.json(configManager.getEmergencyStopState());
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Activate emergency stop
|
|
75
|
+
this.app.post('/api/emergency/stop', (req, res) => {
|
|
76
|
+
configManager.activateEmergencyStop(req.body.reason);
|
|
77
|
+
transactionManager.cancelAllPending();
|
|
78
|
+
res.json({ success: true, message: 'Emergency stop activated' });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Deactivate emergency stop
|
|
82
|
+
this.app.post('/api/emergency/resume', (req, res) => {
|
|
83
|
+
configManager.deactivateEmergencyStop();
|
|
84
|
+
res.json({ success: true, message: 'Emergency stop deactivated' });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Get limits
|
|
88
|
+
this.app.get('/api/limits', (req, res) => {
|
|
89
|
+
res.json({
|
|
90
|
+
limits: configManager.getLimits(),
|
|
91
|
+
timeWindow: configManager.getTimeWindow()
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Webhook endpoints
|
|
96
|
+
this.app.post('/webhooks/wise', (req, res) => {
|
|
97
|
+
console.log('Wise webhook received:', req.body);
|
|
98
|
+
// Process webhook
|
|
99
|
+
res.status(200).send('OK');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.app.post('/webhooks/bunq', (req, res) => {
|
|
103
|
+
console.log('Bunq webhook received:', req.body);
|
|
104
|
+
// Process webhook
|
|
105
|
+
res.status(200).send('OK');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Dashboard route - serve dashboard.html
|
|
109
|
+
this.app.get('/', (req, res) => {
|
|
110
|
+
res.sendFile(path.join(__dirname, '../../dashboard.html'));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.app.get('/dashboard', (req, res) => {
|
|
114
|
+
res.sendFile(path.join(__dirname, '../../dashboard.html'));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
start(): void {
|
|
119
|
+
this.app.listen(this.port, () => {
|
|
120
|
+
console.log(`Payment Skill server running on http://localhost:${this.port}`);
|
|
121
|
+
console.log(`Dashboard: http://localhost:${this.port}/dashboard`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Start server if run directly
|
|
127
|
+
if (require.main === module) {
|
|
128
|
+
const port = parseInt(process.env.PORT || '8080');
|
|
129
|
+
const server = new PaymentSkillServer(port);
|
|
130
|
+
server.start();
|
|
131
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Skill - Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Core types for the payment-skill application
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Merchant Types
|
|
8
|
+
export interface Merchant {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
domains: string[];
|
|
12
|
+
categories: string[];
|
|
13
|
+
apiType: string;
|
|
14
|
+
verified: boolean;
|
|
15
|
+
riskLevel: 'low' | 'medium' | 'high';
|
|
16
|
+
canReceivePayments: boolean;
|
|
17
|
+
apiCapabilities: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MerchantDetectionResult {
|
|
21
|
+
merchant: string;
|
|
22
|
+
detected: boolean;
|
|
23
|
+
apiType?: string;
|
|
24
|
+
capabilities?: string[];
|
|
25
|
+
authType?: string;
|
|
26
|
+
webhookSupport?: boolean;
|
|
27
|
+
asyncConfirmation?: boolean;
|
|
28
|
+
confidence: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Provider Types
|
|
32
|
+
export interface ProviderConfig {
|
|
33
|
+
name: string;
|
|
34
|
+
apiKey: string;
|
|
35
|
+
environment: 'sandbox' | 'production';
|
|
36
|
+
webhookSecret?: string;
|
|
37
|
+
profileId?: string;
|
|
38
|
+
additionalConfig?: Record<string, any>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WiseConfig extends ProviderConfig {
|
|
42
|
+
profileId?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BunqConfig extends ProviderConfig {
|
|
46
|
+
installationToken?: string;
|
|
47
|
+
deviceId?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Transaction Types
|
|
51
|
+
export interface Transaction {
|
|
52
|
+
id: string;
|
|
53
|
+
provider: string;
|
|
54
|
+
merchant: string;
|
|
55
|
+
amount: number;
|
|
56
|
+
currency: string;
|
|
57
|
+
status: TransactionStatus;
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
updatedAt: Date;
|
|
60
|
+
metadata?: Record<string, any>;
|
|
61
|
+
error?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type TransactionStatus =
|
|
65
|
+
| 'pending'
|
|
66
|
+
| 'initiated'
|
|
67
|
+
| 'requires_action'
|
|
68
|
+
| 'processing'
|
|
69
|
+
| 'completed'
|
|
70
|
+
| 'failed'
|
|
71
|
+
| 'cancelled'
|
|
72
|
+
| 'on_hold';
|
|
73
|
+
|
|
74
|
+
// Template Types
|
|
75
|
+
export interface CommandTemplate {
|
|
76
|
+
templateId: string;
|
|
77
|
+
merchant: string;
|
|
78
|
+
version: string;
|
|
79
|
+
description: string;
|
|
80
|
+
prerequisites: TemplatePrerequisites;
|
|
81
|
+
steps: TemplateStep[];
|
|
82
|
+
errorHandling: ErrorHandling;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TemplatePrerequisites {
|
|
86
|
+
apiKey: 'required' | 'optional' | 'none';
|
|
87
|
+
webhookEndpoint?: 'required' | 'recommended' | 'optional';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface TemplateStep {
|
|
91
|
+
order: number;
|
|
92
|
+
name: string;
|
|
93
|
+
command: string;
|
|
94
|
+
params: Record<string, any>;
|
|
95
|
+
output?: Record<string, string>;
|
|
96
|
+
async?: boolean;
|
|
97
|
+
confirmation?: ConfirmationConfig;
|
|
98
|
+
nextStep?: NextStepCondition;
|
|
99
|
+
condition?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ConfirmationConfig {
|
|
103
|
+
type: 'webhook' | 'poll' | 'manual';
|
|
104
|
+
events?: string[];
|
|
105
|
+
timeout?: number;
|
|
106
|
+
pollInterval?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface NextStepCondition {
|
|
110
|
+
ifStatus: string;
|
|
111
|
+
then: number | 'complete';
|
|
112
|
+
else?: number | 'complete';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ErrorHandling {
|
|
116
|
+
retryOn: string[];
|
|
117
|
+
maxRetries: number;
|
|
118
|
+
fallback?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Limit Types
|
|
122
|
+
export interface LimitConfig {
|
|
123
|
+
perTransaction: number;
|
|
124
|
+
daily: number;
|
|
125
|
+
weekly: number;
|
|
126
|
+
monthly: number;
|
|
127
|
+
maxTransactionsPerHour: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface TimeWindowConfig {
|
|
131
|
+
enabled: boolean;
|
|
132
|
+
start: string;
|
|
133
|
+
end: string;
|
|
134
|
+
timezone: string;
|
|
135
|
+
blockedDays?: string[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Emergency Stop Types
|
|
139
|
+
export interface EmergencyStopState {
|
|
140
|
+
active: boolean;
|
|
141
|
+
activatedAt?: Date;
|
|
142
|
+
reason?: string;
|
|
143
|
+
pendingTransactions: string[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// API Response Types
|
|
147
|
+
export interface ApiResponse<T> {
|
|
148
|
+
success: boolean;
|
|
149
|
+
data?: T;
|
|
150
|
+
error?: string;
|
|
151
|
+
message?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Webhook Types
|
|
155
|
+
export interface WebhookEvent {
|
|
156
|
+
id: string;
|
|
157
|
+
merchant: string;
|
|
158
|
+
event: string;
|
|
159
|
+
data: any;
|
|
160
|
+
timestamp: Date;
|
|
161
|
+
signature?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// CLI Types
|
|
165
|
+
export interface CliOptions {
|
|
166
|
+
verbose?: boolean;
|
|
167
|
+
json?: boolean;
|
|
168
|
+
config?: string;
|
|
169
|
+
dryRun?: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Server Types
|
|
173
|
+
export interface ServerConfig {
|
|
174
|
+
port: number;
|
|
175
|
+
host: string;
|
|
176
|
+
webhookPath: string;
|
|
177
|
+
dashboardPath: string;
|
|
178
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"moduleResolution": "node",
|
|
17
|
+
"allowSyntheticDefaultImports": true,
|
|
18
|
+
"experimentalDecorators": true,
|
|
19
|
+
"emitDecoratorMetadata": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/**/*"],
|
|
22
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"lastUpdated": "2026-03-10",
|
|
4
|
+
"description": "Verified merchants list for PaymentSkill - only these merchants are allowed for transactions",
|
|
5
|
+
"merchants": [
|
|
6
|
+
{
|
|
7
|
+
"id": "stripe-connect",
|
|
8
|
+
"name": "Stripe Connect",
|
|
9
|
+
"domains": ["stripe.com", "connect.stripe.com"],
|
|
10
|
+
"categories": ["payments", "finance"],
|
|
11
|
+
"apiType": "Platform API",
|
|
12
|
+
"verified": true,
|
|
13
|
+
"riskLevel": "low",
|
|
14
|
+
"canReceivePayments": true,
|
|
15
|
+
"apiCapabilities": ["create_charges", "create_transfers", "create_payouts", "create_connected_accounts"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "airwallex",
|
|
19
|
+
"name": "Airwallex",
|
|
20
|
+
"domains": ["airwallex.com", "api.airwallex.com"],
|
|
21
|
+
"categories": ["payments", "finance"],
|
|
22
|
+
"apiType": "Connected accounts API",
|
|
23
|
+
"verified": true,
|
|
24
|
+
"riskLevel": "low",
|
|
25
|
+
"canReceivePayments": true,
|
|
26
|
+
"apiCapabilities": ["create_payments", "create_payouts", "create_connected_accounts"]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "digitalocean",
|
|
30
|
+
"name": "DigitalOcean",
|
|
31
|
+
"domains": ["digitalocean.com", "api.digitalocean.com"],
|
|
32
|
+
"categories": ["cloud", "infrastructure", "hosting"],
|
|
33
|
+
"apiType": "Team API",
|
|
34
|
+
"verified": true,
|
|
35
|
+
"riskLevel": "low",
|
|
36
|
+
"canReceivePayments": false,
|
|
37
|
+
"apiCapabilities": ["create_teams", "manage_droplets", "manage_databases"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "clickclack-market",
|
|
41
|
+
"name": "ClickClack Market",
|
|
42
|
+
"domains": ["clickclack.market"],
|
|
43
|
+
"categories": ["marketplace", "shopping"],
|
|
44
|
+
"verified": true,
|
|
45
|
+
"riskLevel": "low",
|
|
46
|
+
"canReceivePayments": true,
|
|
47
|
+
"apiCapabilities": ["custom_api"]
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"blockedCategories": [
|
|
51
|
+
"gambling",
|
|
52
|
+
"adult",
|
|
53
|
+
"drugs",
|
|
54
|
+
"weapons",
|
|
55
|
+
"tobacco"
|
|
56
|
+
],
|
|
57
|
+
"verificationRules": {
|
|
58
|
+
"requireDomainMatch": true,
|
|
59
|
+
"allowSubdomains": true,
|
|
60
|
+
"caseSensitive": false,
|
|
61
|
+
"strictMode": true
|
|
62
|
+
}
|
|
63
|
+
}
|