@striderlabs/mcp-chase 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/README.md +100 -0
- package/dist/auth.js +72 -0
- package/dist/browser.js +394 -0
- package/dist/index.js +398 -0
- package/package.json +39 -0
- package/server.json +20 -0
- package/src/auth.ts +83 -0
- package/src/browser.ts +530 -0
- package/src/index.ts +460 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @striderlabs/mcp-chase
|
|
2
|
+
|
|
3
|
+
MCP server for Chase Bank - let AI agents check accounts, view transactions, and manage finances.
|
|
4
|
+
|
|
5
|
+
Built by [Strider Labs](https://striderlabs.ai) for the agentic commerce era.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Accounts** - View all checking, savings, credit, investment, and loan accounts
|
|
10
|
+
- **Balances** - Get current and available balances
|
|
11
|
+
- **Transactions** - View recent transaction history
|
|
12
|
+
- **Bills** - See payees and scheduled payments
|
|
13
|
+
- **Transfers** - View transfer history and preview new transfers
|
|
14
|
+
- **Statements** - Access account statements
|
|
15
|
+
- **Rewards** - Check Ultimate Rewards points and cash back
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @striderlabs/mcp-chase
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage with Claude Desktop
|
|
24
|
+
|
|
25
|
+
Add to your `claude_desktop_config.json`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"chase": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["@striderlabs/mcp-chase"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Authentication
|
|
39
|
+
|
|
40
|
+
This connector uses browser automation and requires stored cookies:
|
|
41
|
+
|
|
42
|
+
1. Log in to Chase in your browser
|
|
43
|
+
2. Export cookies to `~/.strider/chase/cookies.json`
|
|
44
|
+
3. Use a browser extension like "Cookie-Editor" to export cookies in JSON format
|
|
45
|
+
4. Note: Chase uses MFA - you may need to re-export cookies periodically
|
|
46
|
+
|
|
47
|
+
## Security Notes
|
|
48
|
+
|
|
49
|
+
- This connector operates in **read-only mode** for sensitive operations
|
|
50
|
+
- Transfers and bill payments provide **preview only** with manual completion instructions
|
|
51
|
+
- No actual financial transactions are executed by this connector
|
|
52
|
+
- All credentials are stored locally in `~/.strider/chase/`
|
|
53
|
+
|
|
54
|
+
## Available Tools
|
|
55
|
+
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `chase_auth_check` | Check login status |
|
|
59
|
+
| `chase_auth_clear` | Clear stored session |
|
|
60
|
+
| `chase_accounts` | List all accounts |
|
|
61
|
+
| `chase_balance` | Get account balance |
|
|
62
|
+
| `chase_transactions` | Get transaction history |
|
|
63
|
+
| `chase_bills` | View bill pay payees |
|
|
64
|
+
| `chase_transfers` | View transfer history |
|
|
65
|
+
| `chase_statements` | Get account statements |
|
|
66
|
+
| `chase_rewards` | Check rewards balance |
|
|
67
|
+
| `chase_transfer_preview` | Preview a transfer (manual completion) |
|
|
68
|
+
| `chase_bill_pay_preview` | Preview a bill payment (manual completion) |
|
|
69
|
+
|
|
70
|
+
## Example
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Check if logged in
|
|
74
|
+
const auth = await chase_auth_check();
|
|
75
|
+
|
|
76
|
+
// Get all accounts
|
|
77
|
+
const accounts = await chase_accounts();
|
|
78
|
+
|
|
79
|
+
// Get transactions for an account
|
|
80
|
+
const txns = await chase_transactions({ accountId: accounts[0].id, limit: 10 });
|
|
81
|
+
|
|
82
|
+
// Check rewards
|
|
83
|
+
const rewards = await chase_rewards();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Requirements
|
|
87
|
+
|
|
88
|
+
- Node.js 18+
|
|
89
|
+
- Chase bank account
|
|
90
|
+
- Stored session cookies
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT - Strider Labs
|
|
95
|
+
|
|
96
|
+
## Links
|
|
97
|
+
|
|
98
|
+
- [Strider Labs](https://striderlabs.ai)
|
|
99
|
+
- [GitHub](https://github.com/markswendsen-code/mcp-chase)
|
|
100
|
+
- [npm](https://www.npmjs.com/package/@striderlabs/mcp-chase)
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chase Bank Authentication Utilities
|
|
3
|
+
*
|
|
4
|
+
* Manages cookie storage and session persistence.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import * as os from "os";
|
|
9
|
+
const COOKIES_DIR = path.join(os.homedir(), ".strider", "chase");
|
|
10
|
+
const COOKIES_PATH = path.join(COOKIES_DIR, "cookies.json");
|
|
11
|
+
/**
|
|
12
|
+
* Get the path to the cookies file
|
|
13
|
+
*/
|
|
14
|
+
export function getCookiesPath() {
|
|
15
|
+
return COOKIES_PATH;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if cookies file exists
|
|
19
|
+
*/
|
|
20
|
+
export function hasStoredCookies() {
|
|
21
|
+
return fs.existsSync(COOKIES_PATH);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Save cookies to disk
|
|
25
|
+
*/
|
|
26
|
+
export async function saveCookies(context) {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(COOKIES_DIR)) {
|
|
29
|
+
fs.mkdirSync(COOKIES_DIR, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
const cookies = await context.cookies();
|
|
32
|
+
fs.writeFileSync(COOKIES_PATH, JSON.stringify(cookies, null, 2));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error("Failed to save cookies:", error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load cookies from disk into context
|
|
40
|
+
*/
|
|
41
|
+
export async function loadCookies(context) {
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(COOKIES_PATH)) {
|
|
44
|
+
const cookies = JSON.parse(fs.readFileSync(COOKIES_PATH, "utf-8"));
|
|
45
|
+
await context.addCookies(cookies);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error("Failed to load cookies:", error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Clear stored cookies
|
|
54
|
+
*/
|
|
55
|
+
export function clearCookies() {
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(COOKIES_PATH)) {
|
|
58
|
+
fs.unlinkSync(COOKIES_PATH);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error("Failed to clear cookies:", error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get current auth state
|
|
67
|
+
*/
|
|
68
|
+
export function getAuthState() {
|
|
69
|
+
return {
|
|
70
|
+
isLoggedIn: hasStoredCookies(),
|
|
71
|
+
};
|
|
72
|
+
}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chase Bank Browser Automation
|
|
3
|
+
*
|
|
4
|
+
* Patchright-based stealth automation for Chase banking operations.
|
|
5
|
+
*/
|
|
6
|
+
import { chromium } from "patchright";
|
|
7
|
+
import { saveCookies, loadCookies } from "./auth.js";
|
|
8
|
+
const CHASE_BASE_URL = "https://www.chase.com";
|
|
9
|
+
const DEFAULT_TIMEOUT = 60000;
|
|
10
|
+
// Singleton browser instance
|
|
11
|
+
let browser = null;
|
|
12
|
+
let context = null;
|
|
13
|
+
let page = null;
|
|
14
|
+
/**
|
|
15
|
+
* Initialize browser with stealth settings
|
|
16
|
+
*/
|
|
17
|
+
async function initBrowser() {
|
|
18
|
+
if (browser)
|
|
19
|
+
return;
|
|
20
|
+
browser = await chromium.launch({
|
|
21
|
+
headless: true,
|
|
22
|
+
args: [
|
|
23
|
+
"--disable-blink-features=AutomationControlled",
|
|
24
|
+
"--no-sandbox",
|
|
25
|
+
"--disable-setuid-sandbox",
|
|
26
|
+
"--disable-web-security",
|
|
27
|
+
"--disable-features=IsolateOrigins,site-per-process",
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
context = await browser.newContext({
|
|
31
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
|
32
|
+
viewport: { width: 1280, height: 800 },
|
|
33
|
+
locale: "en-US",
|
|
34
|
+
timezoneId: "America/Los_Angeles",
|
|
35
|
+
});
|
|
36
|
+
// Load saved cookies
|
|
37
|
+
await loadCookies(context);
|
|
38
|
+
page = await context.newPage();
|
|
39
|
+
page.setDefaultTimeout(DEFAULT_TIMEOUT);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the current page, initializing if needed
|
|
43
|
+
*/
|
|
44
|
+
async function getPage() {
|
|
45
|
+
await initBrowser();
|
|
46
|
+
if (!page)
|
|
47
|
+
throw new Error("Failed to initialize browser");
|
|
48
|
+
return page;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Clean up browser resources
|
|
52
|
+
*/
|
|
53
|
+
export async function cleanup() {
|
|
54
|
+
if (context) {
|
|
55
|
+
await saveCookies(context);
|
|
56
|
+
}
|
|
57
|
+
if (browser) {
|
|
58
|
+
await browser.close();
|
|
59
|
+
browser = null;
|
|
60
|
+
context = null;
|
|
61
|
+
page = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get Chase login URL and instructions
|
|
66
|
+
*/
|
|
67
|
+
export async function getLoginUrl() {
|
|
68
|
+
return {
|
|
69
|
+
url: "https://secure.chase.com/web/auth/dashboard",
|
|
70
|
+
instructions: "Please log in to Chase in a browser, then export your cookies to ~/.strider/chase/cookies.json. Use a browser extension like 'Cookie-Editor' to export cookies in JSON format. Note: Chase uses MFA, so you may need to re-export cookies after each session.",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check authentication status
|
|
75
|
+
*/
|
|
76
|
+
export async function checkAuth() {
|
|
77
|
+
try {
|
|
78
|
+
const p = await getPage();
|
|
79
|
+
await p.goto(`${CHASE_BASE_URL}/web/auth/dashboard`, { waitUntil: "networkidle" });
|
|
80
|
+
// Check if we're on the dashboard or redirected to login
|
|
81
|
+
const currentUrl = p.url();
|
|
82
|
+
if (currentUrl.includes("/auth/logon") || currentUrl.includes("/login")) {
|
|
83
|
+
return { isLoggedIn: false };
|
|
84
|
+
}
|
|
85
|
+
// Try to get account holder name
|
|
86
|
+
const holderName = await p.$eval('.mds-header-user-name, .account-holder-name, [data-testid="user-greeting"]', (el) => el.textContent?.trim() || "").catch(() => "");
|
|
87
|
+
return {
|
|
88
|
+
isLoggedIn: true,
|
|
89
|
+
accountHolder: holderName || undefined,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return { isLoggedIn: false };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get all accounts
|
|
98
|
+
*/
|
|
99
|
+
export async function getAccounts() {
|
|
100
|
+
try {
|
|
101
|
+
const p = await getPage();
|
|
102
|
+
await p.goto(`${CHASE_BASE_URL}/web/auth/dashboard`, { waitUntil: "networkidle" });
|
|
103
|
+
await p.waitForTimeout(2000);
|
|
104
|
+
const accounts = await p.$$eval('.account-tile, .account-card, [data-testid="account-tile"]', (elements) => elements.map((el, index) => {
|
|
105
|
+
const nameEl = el.querySelector('.account-name, .tile-header, h3');
|
|
106
|
+
const balanceEl = el.querySelector('.account-balance, .balance, [data-testid="balance"]');
|
|
107
|
+
const lastFourEl = el.querySelector('.account-last-four, .masked-number');
|
|
108
|
+
const typeEl = el.querySelector('.account-type');
|
|
109
|
+
const name = nameEl?.textContent?.trim() || `Account ${index + 1}`;
|
|
110
|
+
const balanceText = balanceEl?.textContent?.trim() || '0';
|
|
111
|
+
const balance = parseFloat(balanceText.replace(/[$,]/g, '')) || 0;
|
|
112
|
+
const lastFour = lastFourEl?.textContent?.trim().replace(/[^0-9]/g, '').slice(-4) || undefined;
|
|
113
|
+
let type = 'checking';
|
|
114
|
+
const typeText = (typeEl?.textContent || name).toLowerCase();
|
|
115
|
+
if (typeText.includes('saving'))
|
|
116
|
+
type = 'savings';
|
|
117
|
+
else if (typeText.includes('credit') || typeText.includes('card'))
|
|
118
|
+
type = 'credit';
|
|
119
|
+
else if (typeText.includes('invest') || typeText.includes('brokerage'))
|
|
120
|
+
type = 'investment';
|
|
121
|
+
else if (typeText.includes('loan') || typeText.includes('mortgage') || typeText.includes('auto'))
|
|
122
|
+
type = 'loan';
|
|
123
|
+
return {
|
|
124
|
+
id: el.getAttribute('data-account-id') || `account-${index}`,
|
|
125
|
+
name,
|
|
126
|
+
type,
|
|
127
|
+
balance,
|
|
128
|
+
lastFour,
|
|
129
|
+
};
|
|
130
|
+
}));
|
|
131
|
+
return { success: true, accounts };
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: error instanceof Error ? error.message : "Failed to get accounts",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get account transactions
|
|
142
|
+
*/
|
|
143
|
+
export async function getTransactions(accountId, limit = 25) {
|
|
144
|
+
try {
|
|
145
|
+
const p = await getPage();
|
|
146
|
+
// Navigate to account details
|
|
147
|
+
await p.goto(`${CHASE_BASE_URL}/web/auth/dashboard`, { waitUntil: "networkidle" });
|
|
148
|
+
await p.waitForTimeout(1000);
|
|
149
|
+
// Click on the account
|
|
150
|
+
const accountTile = await p.$(`[data-account-id="${accountId}"], .account-tile:nth-child(${parseInt(accountId.replace('account-', '')) + 1})`);
|
|
151
|
+
if (accountTile) {
|
|
152
|
+
await accountTile.click();
|
|
153
|
+
await p.waitForNavigation({ waitUntil: "networkidle" }).catch(() => { });
|
|
154
|
+
}
|
|
155
|
+
await p.waitForTimeout(2000);
|
|
156
|
+
const transactions = await p.$$eval('.transaction-row, .activity-row, [data-testid="transaction"]', (elements, maxItems) => elements.slice(0, maxItems).map((el, index) => {
|
|
157
|
+
const dateEl = el.querySelector('.transaction-date, .date, td:first-child');
|
|
158
|
+
const descEl = el.querySelector('.transaction-description, .description, .merchant-name');
|
|
159
|
+
const amountEl = el.querySelector('.transaction-amount, .amount');
|
|
160
|
+
const pendingEl = el.querySelector('.pending, .pending-badge');
|
|
161
|
+
const categoryEl = el.querySelector('.category, .transaction-category');
|
|
162
|
+
const amountText = amountEl?.textContent?.trim() || '0';
|
|
163
|
+
const amount = Math.abs(parseFloat(amountText.replace(/[$,]/g, ''))) || 0;
|
|
164
|
+
const isCredit = amountText.includes('+') || el.classList.contains('credit');
|
|
165
|
+
return {
|
|
166
|
+
id: el.getAttribute('data-transaction-id') || `txn-${index}`,
|
|
167
|
+
date: dateEl?.textContent?.trim() || '',
|
|
168
|
+
description: descEl?.textContent?.trim() || 'Unknown',
|
|
169
|
+
amount,
|
|
170
|
+
type: isCredit ? 'credit' : 'debit',
|
|
171
|
+
category: categoryEl?.textContent?.trim() || undefined,
|
|
172
|
+
pending: !!pendingEl,
|
|
173
|
+
};
|
|
174
|
+
}), limit);
|
|
175
|
+
return { success: true, transactions };
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
success: false,
|
|
180
|
+
error: error instanceof Error ? error.message : "Failed to get transactions",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get account balance
|
|
186
|
+
*/
|
|
187
|
+
export async function getBalance(accountId) {
|
|
188
|
+
try {
|
|
189
|
+
const accountsResult = await getAccounts();
|
|
190
|
+
if (!accountsResult.success || !accountsResult.accounts) {
|
|
191
|
+
return { success: false, error: "Failed to get accounts" };
|
|
192
|
+
}
|
|
193
|
+
const account = accountsResult.accounts.find(a => a.id === accountId);
|
|
194
|
+
if (!account) {
|
|
195
|
+
return { success: false, error: `Account not found: ${accountId}` };
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
balance: account.balance,
|
|
200
|
+
availableBalance: account.availableBalance || account.balance,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
return {
|
|
205
|
+
success: false,
|
|
206
|
+
error: error instanceof Error ? error.message : "Failed to get balance",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get bills/payees
|
|
212
|
+
*/
|
|
213
|
+
export async function getBills() {
|
|
214
|
+
try {
|
|
215
|
+
const p = await getPage();
|
|
216
|
+
await p.goto(`${CHASE_BASE_URL}/web/auth/billpay`, { waitUntil: "networkidle" });
|
|
217
|
+
await p.waitForTimeout(2000);
|
|
218
|
+
const bills = await p.$$eval('.payee-row, .bill-row, [data-testid="payee"]', (elements) => elements.map((el, index) => {
|
|
219
|
+
const payeeEl = el.querySelector('.payee-name, .name');
|
|
220
|
+
const amountEl = el.querySelector('.amount, .payment-amount');
|
|
221
|
+
const dueDateEl = el.querySelector('.due-date, .date');
|
|
222
|
+
const statusEl = el.querySelector('.status, .payment-status');
|
|
223
|
+
const autopayEl = el.querySelector('.autopay, .auto-pay-enabled');
|
|
224
|
+
const amountText = amountEl?.textContent?.trim() || '0';
|
|
225
|
+
const amount = parseFloat(amountText.replace(/[$,]/g, '')) || 0;
|
|
226
|
+
let status = 'scheduled';
|
|
227
|
+
const statusText = statusEl?.textContent?.toLowerCase() || '';
|
|
228
|
+
if (statusText.includes('paid'))
|
|
229
|
+
status = 'paid';
|
|
230
|
+
else if (statusText.includes('overdue') || statusText.includes('past due'))
|
|
231
|
+
status = 'overdue';
|
|
232
|
+
return {
|
|
233
|
+
id: el.getAttribute('data-payee-id') || `bill-${index}`,
|
|
234
|
+
payee: payeeEl?.textContent?.trim() || 'Unknown Payee',
|
|
235
|
+
amount,
|
|
236
|
+
dueDate: dueDateEl?.textContent?.trim() || '',
|
|
237
|
+
status,
|
|
238
|
+
autopay: !!autopayEl,
|
|
239
|
+
};
|
|
240
|
+
}));
|
|
241
|
+
return { success: true, bills };
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
error: error instanceof Error ? error.message : "Failed to get bills",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get transfer history
|
|
252
|
+
*/
|
|
253
|
+
export async function getTransfers() {
|
|
254
|
+
try {
|
|
255
|
+
const p = await getPage();
|
|
256
|
+
await p.goto(`${CHASE_BASE_URL}/web/auth/transfer`, { waitUntil: "networkidle" });
|
|
257
|
+
await p.waitForTimeout(2000);
|
|
258
|
+
const transfers = await p.$$eval('.transfer-row, .activity-row, [data-testid="transfer"]', (elements) => elements.slice(0, 20).map((el, index) => {
|
|
259
|
+
const fromEl = el.querySelector('.from-account, .source');
|
|
260
|
+
const toEl = el.querySelector('.to-account, .destination');
|
|
261
|
+
const amountEl = el.querySelector('.amount');
|
|
262
|
+
const dateEl = el.querySelector('.date, .transfer-date');
|
|
263
|
+
const statusEl = el.querySelector('.status');
|
|
264
|
+
const amountText = amountEl?.textContent?.trim() || '0';
|
|
265
|
+
const amount = parseFloat(amountText.replace(/[$,]/g, '')) || 0;
|
|
266
|
+
let status = 'completed';
|
|
267
|
+
const statusText = statusEl?.textContent?.toLowerCase() || '';
|
|
268
|
+
if (statusText.includes('pending'))
|
|
269
|
+
status = 'pending';
|
|
270
|
+
else if (statusText.includes('failed') || statusText.includes('cancelled'))
|
|
271
|
+
status = 'failed';
|
|
272
|
+
return {
|
|
273
|
+
id: el.getAttribute('data-transfer-id') || `transfer-${index}`,
|
|
274
|
+
fromAccount: fromEl?.textContent?.trim() || 'Unknown',
|
|
275
|
+
toAccount: toEl?.textContent?.trim() || 'Unknown',
|
|
276
|
+
amount,
|
|
277
|
+
date: dateEl?.textContent?.trim() || '',
|
|
278
|
+
status,
|
|
279
|
+
};
|
|
280
|
+
}));
|
|
281
|
+
return { success: true, transfers };
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: error instanceof Error ? error.message : "Failed to get transfers",
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get statements
|
|
292
|
+
*/
|
|
293
|
+
export async function getStatements(accountId) {
|
|
294
|
+
try {
|
|
295
|
+
const p = await getPage();
|
|
296
|
+
// Navigate to account and then statements
|
|
297
|
+
await p.goto(`${CHASE_BASE_URL}/web/auth/dashboard`, { waitUntil: "networkidle" });
|
|
298
|
+
await p.waitForTimeout(1000);
|
|
299
|
+
// This would need to navigate to the specific account's statements section
|
|
300
|
+
// For now, return a message about navigation
|
|
301
|
+
const statements = await p.$$eval('.statement-row, [data-testid="statement"]', (elements, acctId) => elements.slice(0, 12).map((el, index) => {
|
|
302
|
+
const periodEl = el.querySelector('.period, .statement-period');
|
|
303
|
+
const dateEl = el.querySelector('.date, .statement-date');
|
|
304
|
+
return {
|
|
305
|
+
id: el.getAttribute('data-statement-id') || `stmt-${index}`,
|
|
306
|
+
accountId: acctId,
|
|
307
|
+
period: periodEl?.textContent?.trim() || '',
|
|
308
|
+
date: dateEl?.textContent?.trim() || '',
|
|
309
|
+
};
|
|
310
|
+
}), accountId);
|
|
311
|
+
return { success: true, statements };
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
return {
|
|
315
|
+
success: false,
|
|
316
|
+
error: error instanceof Error ? error.message : "Failed to get statements",
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Get rewards/points info
|
|
322
|
+
*/
|
|
323
|
+
export async function getRewards() {
|
|
324
|
+
try {
|
|
325
|
+
const p = await getPage();
|
|
326
|
+
await p.goto(`${CHASE_BASE_URL}/web/auth/dashboard`, { waitUntil: "networkidle" });
|
|
327
|
+
await p.waitForTimeout(2000);
|
|
328
|
+
const rewards = await p.evaluate(() => {
|
|
329
|
+
const pointsEl = document.querySelector('.points-balance, .ultimate-rewards, [data-testid="points"]');
|
|
330
|
+
const cashBackEl = document.querySelector('.cash-back-balance, [data-testid="cashback"]');
|
|
331
|
+
const tierEl = document.querySelector('.rewards-tier, .membership-level');
|
|
332
|
+
const pointsText = pointsEl?.textContent?.trim() || '0';
|
|
333
|
+
const cashBackText = cashBackEl?.textContent?.trim() || '0';
|
|
334
|
+
return {
|
|
335
|
+
pointsBalance: parseInt(pointsText.replace(/[^0-9]/g, '')) || 0,
|
|
336
|
+
cashBackBalance: parseFloat(cashBackText.replace(/[$,]/g, '')) || 0,
|
|
337
|
+
pendingRewards: 0,
|
|
338
|
+
tier: tierEl?.textContent?.trim() || undefined,
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
return { success: true, rewards };
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
return {
|
|
345
|
+
success: false,
|
|
346
|
+
error: error instanceof Error ? error.message : "Failed to get rewards",
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Initiate a transfer (preview only - does not execute)
|
|
352
|
+
*/
|
|
353
|
+
export async function initiateTransfer(fromAccountId, toAccountId, amount) {
|
|
354
|
+
// For security, this only provides a preview and instructions
|
|
355
|
+
// The actual transfer would need to be confirmed by the user
|
|
356
|
+
return {
|
|
357
|
+
success: true,
|
|
358
|
+
preview: {
|
|
359
|
+
fromAccountId,
|
|
360
|
+
toAccountId,
|
|
361
|
+
amount,
|
|
362
|
+
message: "For security reasons, transfers must be completed manually in Chase. Navigate to Transfers in your Chase account to complete this transfer.",
|
|
363
|
+
instructions: [
|
|
364
|
+
"1. Log in to Chase.com or Chase mobile app",
|
|
365
|
+
"2. Go to 'Pay & Transfer' > 'Transfers'",
|
|
366
|
+
"3. Select your from and to accounts",
|
|
367
|
+
`4. Enter amount: $${amount.toFixed(2)}`,
|
|
368
|
+
"5. Review and confirm the transfer"
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Pay a bill (preview only - does not execute)
|
|
375
|
+
*/
|
|
376
|
+
export async function payBill(payeeId, amount, date) {
|
|
377
|
+
// For security, this only provides a preview and instructions
|
|
378
|
+
return {
|
|
379
|
+
success: true,
|
|
380
|
+
preview: {
|
|
381
|
+
payeeId,
|
|
382
|
+
amount,
|
|
383
|
+
scheduledDate: date || "Immediately",
|
|
384
|
+
message: "For security reasons, bill payments must be completed manually in Chase.",
|
|
385
|
+
instructions: [
|
|
386
|
+
"1. Log in to Chase.com or Chase mobile app",
|
|
387
|
+
"2. Go to 'Pay & Transfer' > 'Pay Bills'",
|
|
388
|
+
"3. Select the payee",
|
|
389
|
+
`4. Enter amount: $${amount.toFixed(2)}`,
|
|
390
|
+
"5. Choose payment date and confirm"
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|