joopjs 2.0.4 → 2.0.6
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/.claude/skills/auth.md +235 -0
- package/.claude/skills/banking.md +377 -0
- package/.claude/skills/encryption.md +265 -0
- package/.claude/skills/finance.md +248 -0
- package/.claude/skills/observables.md +242 -0
- package/.claude/skills/security.md +240 -0
- package/.claude/skills/setup.md +185 -0
- package/.cursor/rules/joopjs.mdc +151 -0
- package/.github/copilot-instructions.md +141 -0
- package/.windsurf/rules/joopjs.md +222 -0
- package/CHANGELOG.md +52 -0
- package/README.md +29 -1
- package/ai-rules/AGENTS.md +220 -0
- package/ai-rules/GEMINI.md +169 -0
- package/package.json +85 -27
- package/scripts/setup-ai.mjs +133 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# /auth — JoopJS Authentication Services
|
|
2
|
+
|
|
3
|
+
> Author: Kundan Singh
|
|
4
|
+
|
|
5
|
+
All auth services are imported from `'joopjs'`. Instantiate as plain singletons.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Auth Service
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { JoopAuthService } from 'joopjs';
|
|
13
|
+
const auth = new JoopAuthService();
|
|
14
|
+
|
|
15
|
+
const session = await auth.login('alice@bank.com', 'password123');
|
|
16
|
+
// session: { userId, sessionId, accessToken, refreshToken, expiresAt }
|
|
17
|
+
|
|
18
|
+
const refreshed = await auth.refresh(session.refreshToken);
|
|
19
|
+
await auth.logout(session.sessionId);
|
|
20
|
+
|
|
21
|
+
const me = auth.getCurrentUser(); // JoopAuthUser | null
|
|
22
|
+
auth.session$().subscribe(session => { }); // reactive session changes
|
|
23
|
+
|
|
24
|
+
auth.onEvent('login', e => console.log('Login:', e.userId));
|
|
25
|
+
auth.onEvent('logout', e => console.log('Logout'));
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Key types:** `JoopAuthUser`, `JoopAuthSession`, `JoopAuthToken`, `JoopAuthEvent`
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## OTP Service
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { JoopOtpService } from 'joopjs';
|
|
36
|
+
const otp = new JoopOtpService({ expiryMs: 5 * 60_000, length: 6 });
|
|
37
|
+
|
|
38
|
+
const token = otp.generate('u-001', 'login'); // { otp: '483920', expiresAt }
|
|
39
|
+
const ok = otp.verify('u-001', 'login', '483920'); // boolean
|
|
40
|
+
// auto-invalidated after single use
|
|
41
|
+
|
|
42
|
+
// TOTP (time-based)
|
|
43
|
+
const secret = otp.generateTotpSecret(); // base32 secret
|
|
44
|
+
const qrUri = otp.getTotpUri('alice@bank.com', secret, 'MyBank');
|
|
45
|
+
const valid = otp.verifyTotp(secret, userEnteredCode);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## JWT Service
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { JoopJwtService } from 'joopjs';
|
|
54
|
+
const jwt = new JoopJwtService({ secret: 'my-secret', expiryMs: 3600_000 });
|
|
55
|
+
|
|
56
|
+
const token = jwt.sign({ userId: 'u-001', role: 'admin' });
|
|
57
|
+
const claims = jwt.verify(token); // { userId, role, iat, exp } | null
|
|
58
|
+
const decoded = jwt.decode(token); // decode without verification
|
|
59
|
+
const renewed = jwt.renew(token); // extends expiry
|
|
60
|
+
const revoked = jwt.revoke(token); // blacklists token
|
|
61
|
+
const isRevoked = jwt.isRevoked(token); // boolean
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## MFA Service
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { JoopMfaService } from 'joopjs';
|
|
70
|
+
const mfa = new JoopMfaService();
|
|
71
|
+
|
|
72
|
+
mfa.enroll('u-001', 'totp'); // or 'sms', 'email', 'hardware-key'
|
|
73
|
+
const challenge = mfa.challenge('u-001'); // { challengeId, method, hint }
|
|
74
|
+
const result = mfa.respond(challenge.challengeId, userCode);
|
|
75
|
+
// result: { passed: true, sessionUpgraded: true }
|
|
76
|
+
|
|
77
|
+
const methods = mfa.getEnrolledMethods('u-001'); // ['totp']
|
|
78
|
+
mfa.unenroll('u-001', 'totp');
|
|
79
|
+
mfa.status$().subscribe(event => { });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## PKCE / OAuth 2.0
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { JoopPkceService } from 'joopjs';
|
|
88
|
+
const pkce = new JoopPkceService({
|
|
89
|
+
clientId: 'my-app',
|
|
90
|
+
redirectUri: 'https://app.example.com/callback',
|
|
91
|
+
authorizationEndpoint: 'https://auth.bank.com/authorize',
|
|
92
|
+
tokenEndpoint: 'https://auth.bank.com/token',
|
|
93
|
+
scopes: ['openid', 'profile', 'accounts'],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const { url, codeVerifier, state } = pkce.buildAuthorizationUrl();
|
|
97
|
+
// Redirect user to `url`, store codeVerifier + state
|
|
98
|
+
|
|
99
|
+
const tokens = await pkce.exchangeCode(authorizationCode, codeVerifier);
|
|
100
|
+
// { accessToken, refreshToken, idToken, expiresIn }
|
|
101
|
+
|
|
102
|
+
const refreshed = await pkce.refreshTokens(tokens.refreshToken);
|
|
103
|
+
const claims = pkce.parseIdToken(tokens.idToken); // OIDC claims
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## OIDC Service
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { JoopOidcService } from 'joopjs';
|
|
112
|
+
const oidc = new JoopOidcService({ issuer: 'https://auth.bank.com', clientId: 'my-app' });
|
|
113
|
+
|
|
114
|
+
await oidc.loadDiscovery(); // fetches /.well-known/openid-configuration
|
|
115
|
+
const url = oidc.buildLoginUrl({ redirectUri: 'https://app.example.com/callback', scopes: ['openid', 'email'] });
|
|
116
|
+
const user = await oidc.processCallback(callbackUrl); // JoopOidcUser
|
|
117
|
+
const info = await oidc.getUserInfo(accessToken);
|
|
118
|
+
await oidc.endSession(idToken, 'https://app.example.com');
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## SSO Service
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { JoopSsoService } from 'joopjs';
|
|
127
|
+
const sso = new JoopSsoService();
|
|
128
|
+
|
|
129
|
+
sso.registerProvider({ id: 'azure-ad', name: 'Azure AD', type: 'oidc',
|
|
130
|
+
clientId: '...', clientSecret: '...', discoveryUrl: 'https://login.microsoftonline.com/.../v2.0' });
|
|
131
|
+
|
|
132
|
+
const loginUrl = sso.getLoginUrl('azure-ad', { redirectUri: '/callback' });
|
|
133
|
+
const session = await sso.handleCallback('azure-ad', callbackParams);
|
|
134
|
+
// session.user: { id, email, name, groups, provider }
|
|
135
|
+
|
|
136
|
+
sso.setRoleMapping('azure-ad', { 'BankAdmins': 'admin', 'BankUsers': 'user' });
|
|
137
|
+
const appRole = sso.mapRole('azure-ad', 'BankAdmins'); // 'admin'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Biometric Auth
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { JoopBiometricAuthService } from 'joopjs';
|
|
146
|
+
const bio = new JoopBiometricAuthService();
|
|
147
|
+
|
|
148
|
+
const available = await bio.isAvailable(); // { fingerprint, faceId, iris }
|
|
149
|
+
await bio.enroll('u-001', 'fingerprint');
|
|
150
|
+
const result = await bio.authenticate('u-001', 'fingerprint');
|
|
151
|
+
// { authenticated: true, confidence: 0.98, method: 'fingerprint' }
|
|
152
|
+
|
|
153
|
+
bio.revoke('u-001', 'fingerprint');
|
|
154
|
+
bio.authEvent$().subscribe(event => { });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Permission / RBAC
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { JoopPermissionService } from 'joopjs';
|
|
163
|
+
const perms = new JoopPermissionService();
|
|
164
|
+
|
|
165
|
+
perms.defineRole('admin', ['accounts:read', 'accounts:write', 'transfers:approve']);
|
|
166
|
+
perms.defineRole('teller', ['accounts:read', 'transfers:initiate']);
|
|
167
|
+
|
|
168
|
+
perms.assignRole('u-001', 'admin');
|
|
169
|
+
perms.grantPermission('u-002', 'reports:read');
|
|
170
|
+
|
|
171
|
+
const can = perms.check('u-001', 'transfers:approve'); // true
|
|
172
|
+
const all = perms.getPermissions('u-001'); // string[]
|
|
173
|
+
perms.revokeRole('u-001', 'admin');
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Rate Limiter (Auth Protection)
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { JoopRateLimiterService } from 'joopjs';
|
|
182
|
+
const rl = new JoopRateLimiterService();
|
|
183
|
+
|
|
184
|
+
rl.configure('login', { maxAttempts: 5, windowMs: 15 * 60_000, blockDurationMs: 30 * 60_000 });
|
|
185
|
+
|
|
186
|
+
const result = rl.check('login', 'u-001');
|
|
187
|
+
// { allowed: true, remaining: 4, resetAt: number }
|
|
188
|
+
|
|
189
|
+
rl.record('login', 'u-001', false); // record failed attempt
|
|
190
|
+
rl.record('login', 'u-001', true); // success — resets counter
|
|
191
|
+
rl.block('login', 'u-001'); // force block
|
|
192
|
+
const status = rl.getStatus('login', 'u-001');
|
|
193
|
+
// { blocked: false, attempts: 2, remaining: 3, resetAt }
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Session Management
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { JoopSessionService } from 'joopjs';
|
|
202
|
+
const sessions = new JoopSessionService({ idleTimeoutMs: 15 * 60_000, absoluteTimeoutMs: 8 * 3600_000 });
|
|
203
|
+
|
|
204
|
+
const s = sessions.create({ userId: 'u-001', ipAddress: '10.0.0.1', userAgent: 'Chrome/120' });
|
|
205
|
+
sessions.touch(s.id); // reset idle timer
|
|
206
|
+
const valid = sessions.validate(s.id); // { valid, reason? }
|
|
207
|
+
sessions.invalidate(s.id);
|
|
208
|
+
sessions.invalidateAll('u-001'); // logout all devices
|
|
209
|
+
sessions.expired$().subscribe(s => console.log('Session expired:', s.id));
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## What Gets Published to npm
|
|
215
|
+
|
|
216
|
+
Controlled by `"files"` in `package.json` — acts as an allowlist. Only these two are included:
|
|
217
|
+
|
|
218
|
+
| Published to npm | Never published |
|
|
219
|
+
|-----------------|----------------|
|
|
220
|
+
| `dist/` — ESM + CJS + `.d.ts` for all 34 sub-paths | `src/` — TypeScript source |
|
|
221
|
+
| `CHANGELOG.md` — release history | `tests/` — test suite |
|
|
222
|
+
| | `scripts/` — release automation |
|
|
223
|
+
| | `playground/` — Vite demo app |
|
|
224
|
+
| | `.claude/` — Claude skills (including this file) |
|
|
225
|
+
| | `.cursor/` — Cursor rules |
|
|
226
|
+
| | `.windsurf/` — Windsurf rules |
|
|
227
|
+
| | `GEMINI.md`, `AGENTS.md` — AI tool instructions |
|
|
228
|
+
| | `tsup.config.ts`, `vitest.config.ts`, `tsconfig.json` |
|
|
229
|
+
|
|
230
|
+
Source code, AI rules, and dev tooling are **never** published to npm.
|
|
231
|
+
|
|
232
|
+
Verify the tarball contents before any publish:
|
|
233
|
+
```bash
|
|
234
|
+
npm pack --dry-run
|
|
235
|
+
```
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# /banking — JoopJS Banking Services
|
|
2
|
+
|
|
3
|
+
> Author: Kundan Singh
|
|
4
|
+
|
|
5
|
+
All banking services are imported from `'joopjs'`. Instantiate as plain singletons.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Digital Wallet
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { JoopDigitalWalletService } from 'joopjs';
|
|
13
|
+
const wallet = new JoopDigitalWalletService();
|
|
14
|
+
|
|
15
|
+
const w = wallet.createWallet('user-001', { currency: 'USD', label: 'Primary' });
|
|
16
|
+
wallet.topUp(w.id, 500);
|
|
17
|
+
wallet.pay(w.id, 50, 'merchant-1', 'Coffee Shop');
|
|
18
|
+
wallet.transfer(w.id, otherWalletId, 100);
|
|
19
|
+
wallet.freeze(w.id); wallet.unfreeze(w.id); wallet.close(w.id); // requires zero balance
|
|
20
|
+
const bal = wallet.getBalance(w.id); // number
|
|
21
|
+
const txns = wallet.getTransactions(w.id, 20); // last 20, newest first
|
|
22
|
+
wallet.balance$().subscribe(({ walletId, balance }) => { }); // reactive
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Key types:** `JoopWallet`, `JoopWalletTransaction`, `JoopWalletStatus`, `JoopWalletTxnType`, `JoopTransferResult`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Loan Servicing
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { JoopLoanServicingService } from 'joopjs';
|
|
33
|
+
const loans = new JoopLoanServicingService();
|
|
34
|
+
|
|
35
|
+
const loan = loans.createLoan({
|
|
36
|
+
borrowerName: 'Alice', borrowerId: 'u-001',
|
|
37
|
+
principalAmount: 120_000, annualInterestRatePercent: 12, tenureMonths: 24,
|
|
38
|
+
currency: 'USD',
|
|
39
|
+
});
|
|
40
|
+
// loan.ref → 'LN-2026-000001', loan.emiAmount auto-calculated
|
|
41
|
+
|
|
42
|
+
loans.recordPayment(loan.id, loan.emiAmount); // interest-first allocation
|
|
43
|
+
const sched = loans.getSchedule(loan.id); // JoopInstallment[]
|
|
44
|
+
const stmt = loans.getLoanStatement(loan.id); // full statement
|
|
45
|
+
const bal = loans.getOutstandingBalance(loan.id); // number
|
|
46
|
+
loans.markDefaulted(loan.id);
|
|
47
|
+
loans.waveInstallment(loan.id, 3); // waive installment #3
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Key types:** `JoopLoanAccount`, `JoopInstallment`, `JoopLoanPayment`, `JoopLoanStatement`, `JoopLoanAccountStatus`, `JoopRepaymentStatus`
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## FX Forward Contracts
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { JoopFxForwardService } from 'joopjs';
|
|
58
|
+
const fx = new JoopFxForwardService();
|
|
59
|
+
|
|
60
|
+
fx.setSpotRate('USD', 'EUR', 0.92); // auto-creates inverse
|
|
61
|
+
const fwd = fx.createForward({
|
|
62
|
+
type: 'sell', baseCurrency: 'USD', quoteCurrency: 'EUR',
|
|
63
|
+
notionalAmount: 100_000, contractRate: 0.92,
|
|
64
|
+
maturityDate: Date.now() + 90 * 86_400_000,
|
|
65
|
+
});
|
|
66
|
+
// fwd.ref → 'FWD-2026-000001'
|
|
67
|
+
|
|
68
|
+
const settlement = fx.settleForward(fwd.id, 0.90); // settlement.pnl = 2000
|
|
69
|
+
fx.cancelForward(fwd.id);
|
|
70
|
+
const mtm = fx.getMarkToMarket(fwd.id); // unrealized P&L
|
|
71
|
+
const exp = fx.getExposure('USD'); // JoopFxExposure[]
|
|
72
|
+
const expiring = fx.getExpiring(7 * 86_400_000); // due in 7 days
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Key types:** `JoopFxForward`, `JoopForwardSettlement`, `JoopFxExposure`, `JoopForwardType`, `JoopForwardStatus`
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Ledger (Double-Entry Bookkeeping)
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { JoopLedgerService } from 'joopjs';
|
|
83
|
+
const ledger = new JoopLedgerService();
|
|
84
|
+
|
|
85
|
+
ledger.addAccount('1001', 'Cash', 'asset');
|
|
86
|
+
ledger.addAccount('4001', 'Sales Revenue','revenue');
|
|
87
|
+
|
|
88
|
+
const entry = ledger.postEntry('Sale', [
|
|
89
|
+
{ accountCode: '1001', debit: 1000, credit: 0 },
|
|
90
|
+
{ accountCode: '4001', debit: 0, credit: 1000 },
|
|
91
|
+
]);
|
|
92
|
+
// entry.ref → 'JE-2026-000001'
|
|
93
|
+
|
|
94
|
+
const bal = ledger.getBalance('1001'); // 1000
|
|
95
|
+
const trial = ledger.getTrialBalance(); // { rows, totalDebits, totalCredits, isBalanced }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Key types:** `JoopAccountType` ('asset'|'liability'|'equity'|'revenue'|'expense'), `JoopLedgerAccount`, `JoopJournalEntry`, `JoopTrialBalance`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Reconciliation
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { JoopReconciliationService } from 'joopjs';
|
|
106
|
+
const recon = new JoopReconciliationService();
|
|
107
|
+
|
|
108
|
+
const session = recon.createSession('ACC-001', bankItems, internalItems);
|
|
109
|
+
recon.autoMatch(session.id, 0, 3); // tolerance: $0, 3 days
|
|
110
|
+
recon.manualMatch(session.id, internalId, bankId);
|
|
111
|
+
const summary = recon.getSummary(session.id);
|
|
112
|
+
// { matchedPairs, unmatchedInternal, unmatchedBank, totalDifference, isReconciled }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Limit Management
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { JoopLimitManagementService } from 'joopjs';
|
|
121
|
+
const limits = new JoopLimitManagementService();
|
|
122
|
+
|
|
123
|
+
limits.setLimit({
|
|
124
|
+
scope: 'account', scopeId: 'ACC-001', type: 'daily',
|
|
125
|
+
currency: 'USD', maxAmount: 10_000, enabled: true,
|
|
126
|
+
});
|
|
127
|
+
const check = limits.checkLimit('account', 'ACC-001', 'daily', 'USD', 5000);
|
|
128
|
+
// null = no limit configured; { allowed, remaining, used } if configured
|
|
129
|
+
limits.recordUsage('account', 'ACC-001', 'daily', 'USD', 5000);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Key types:** `JoopLimitType` ('per-transaction'|'daily'|'weekly'|'monthly'|'yearly'), `JoopLimitScope`, `JoopLimit`, `JoopLimitCheckResult`
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Standing Orders
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { JoopStandingOrderService } from 'joopjs';
|
|
140
|
+
const so = new JoopStandingOrderService();
|
|
141
|
+
|
|
142
|
+
const order = so.create({
|
|
143
|
+
fromAccount: 'ACC-001', toAccount: 'ACC-002',
|
|
144
|
+
toBeneficiaryName: 'Rent', amount: 1500,
|
|
145
|
+
frequency: 'monthly', startDate: Date.now(),
|
|
146
|
+
currency: 'USD',
|
|
147
|
+
});
|
|
148
|
+
// order.ref → 'SO-2026-000001'
|
|
149
|
+
|
|
150
|
+
const exec = so.execute(order.id, 'success'); // advances nextExecutionAt
|
|
151
|
+
const due = so.getDue(); // orders due now
|
|
152
|
+
so.pause(order.id); so.resume(order.id); so.cancel(order.id);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Insurance
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { JoopInsuranceService } from 'joopjs';
|
|
161
|
+
const ins = new JoopInsuranceService();
|
|
162
|
+
|
|
163
|
+
const policy = ins.addPolicy({
|
|
164
|
+
type: 'life', holderName: 'Alice', holderId: 'u-001',
|
|
165
|
+
coverageAmount: 500_000, annualPremium: 2400,
|
|
166
|
+
startDate: Date.now(), endDate: Date.now() + 365 * 86_400_000,
|
|
167
|
+
frequency: 'monthly',
|
|
168
|
+
});
|
|
169
|
+
ins.fileClaim(policy.id, 1000, 'Hospital stay'); // claimNumber CLM-2026-000001
|
|
170
|
+
ins.renewPolicy(policy.id, Date.now() + 730 * 86_400_000);
|
|
171
|
+
ins.recordPremiumPayment(policy.id, 200);
|
|
172
|
+
const expiring = ins.getExpiring(30 * 86_400_000); // expiring in 30 days
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Remittance
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { JoopRemittanceService } from 'joopjs';
|
|
181
|
+
const rem = new JoopRemittanceService();
|
|
182
|
+
|
|
183
|
+
rem.setExchangeRate('USD', 'INR', 83.5);
|
|
184
|
+
const quote = rem.getQuote('USD', 'INR', 1000);
|
|
185
|
+
// { sendAmount:1000, fee:25, receiveAmount:81543.75, exchangeRate:83.5 }
|
|
186
|
+
|
|
187
|
+
const tx = rem.initiate({
|
|
188
|
+
senderId: 'u-001', senderName: 'Alice',
|
|
189
|
+
receiverId: 'u-002', receiverName: 'Bob',
|
|
190
|
+
sourceCurrency: 'USD', targetCurrency: 'INR',
|
|
191
|
+
sendAmount: 1000, channel: 'swift',
|
|
192
|
+
});
|
|
193
|
+
// tx.ref → 'RMT-2026-000001'
|
|
194
|
+
|
|
195
|
+
rem.updateStatus(tx.id, 'completed');
|
|
196
|
+
const history = rem.getHistory('u-001', 5); // last 5, newest first
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Virtual Accounts
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { JoopVirtualAccountService } from 'joopjs';
|
|
205
|
+
const va = new JoopVirtualAccountService();
|
|
206
|
+
|
|
207
|
+
const acct = va.create({ parentAccountId: 'ACC-001', purpose: 'escrow', currency: 'USD', expectedAmount: 5000 });
|
|
208
|
+
va.recordCredit(acct.id, 2500, { reference: 'INV-001' });
|
|
209
|
+
const full = va.isFullyCollected(acct.id); // false (2500 of 5000)
|
|
210
|
+
va.close(acct.id);
|
|
211
|
+
const byRef = va.getByReference(acct.accountNumber);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Card Management
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { JoopCardManagementService } from 'joopjs';
|
|
220
|
+
const cards = new JoopCardManagementService();
|
|
221
|
+
|
|
222
|
+
const card = cards.issueCard({ userId: 'u-001', cardType: 'virtual', network: 'visa', currency: 'USD' });
|
|
223
|
+
cards.freeze(card.id); cards.unfreeze(card.id);
|
|
224
|
+
cards.setSpendingLimits(card.id, { daily: 500, monthly: 5000, perTransaction: 200 });
|
|
225
|
+
cards.setControls(card.id, { onlineAllowed: true, internationalAllowed: false });
|
|
226
|
+
const check = cards.checkTransaction(card.id, { amount: 100, merchant: 'Amazon', channel: 'online' });
|
|
227
|
+
// { allowed: true, reason: null }
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Dispute Management
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { JoopDisputeService } from 'joopjs';
|
|
236
|
+
const disputes = new JoopDisputeService();
|
|
237
|
+
|
|
238
|
+
const d = disputes.file({ userId: 'u-001', transactionId: 'TXN-001', reason: 'unauthorized', amount: 99.99 });
|
|
239
|
+
disputes.uploadEvidence(d.id, { type: 'screenshot', description: 'Charge I did not make' });
|
|
240
|
+
disputes.resolve(d.id, 'resolved-in-favor', 'Refund issued');
|
|
241
|
+
const stats = disputes.getStats();
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Bill Payment
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { JoopBillPaymentService } from 'joopjs';
|
|
250
|
+
const bills = new JoopBillPaymentService();
|
|
251
|
+
|
|
252
|
+
bills.addBiller({ id: 'ELEC-001', name: 'City Electric', category: 'utilities', accountFormat: 'ACCT-####' });
|
|
253
|
+
const bill = bills.createBill({ billerId: 'ELEC-001', accountNumber: 'ACCT-1234', amount: 150, dueDate: Date.now() + 7 * 86_400_000 });
|
|
254
|
+
const payment = bills.pay(bill.id, 150, 'wallet');
|
|
255
|
+
const upcoming = bills.getUpcoming(7 * 86_400_000); // due in 7 days
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Statement Generator
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { JoopStatementGeneratorService } from 'joopjs';
|
|
264
|
+
const gen = new JoopStatementGeneratorService();
|
|
265
|
+
|
|
266
|
+
gen.addTransaction({ id: 'T1', date: Date.now(), amount: -50, description: 'Coffee', type: 'debit', balance: 950 });
|
|
267
|
+
const stmt = gen.generate({ accountId: 'ACC-001', from: startTs, to: endTs, format: 'pdf' });
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Beneficiary
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { JoopBeneficiaryService } from 'joopjs';
|
|
276
|
+
const bene = new JoopBeneficiaryService();
|
|
277
|
+
|
|
278
|
+
const b = bene.add({ userId: 'u-001', name: 'Bob', type: 'bank', accountNumber: '12345678', bankCode: 'HSBC' });
|
|
279
|
+
const v = bene.validate(b.id); // { valid, errors }
|
|
280
|
+
const list = bene.getAll('u-001');
|
|
281
|
+
bene.remove(b.id);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Mandate (Direct Debit)
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { JoopMandateService } from 'joopjs';
|
|
290
|
+
const mandates = new JoopMandateService();
|
|
291
|
+
|
|
292
|
+
const m = mandates.create({ userId: 'u-001', creditorName: 'Netflix', amount: 15.99, currency: 'USD', frequency: 'monthly', startDate: Date.now() });
|
|
293
|
+
mandates.execute(m.id, 15.99); // records execution
|
|
294
|
+
mandates.pause(m.id); mandates.resume(m.id); mandates.cancel(m.id);
|
|
295
|
+
const due = mandates.getDue();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Split Payment
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { JoopSplitPaymentService } from 'joopjs';
|
|
304
|
+
const split = new JoopSplitPaymentService();
|
|
305
|
+
|
|
306
|
+
const exp = split.createExpense({ title: 'Dinner', totalAmount: 120, paidById: 'u-001', method: 'equal' });
|
|
307
|
+
split.addParticipant(exp.id, { userId: 'u-002', name: 'Bob' });
|
|
308
|
+
split.addParticipant(exp.id, { userId: 'u-003', name: 'Carol' });
|
|
309
|
+
split.settle(exp.id, 'u-002'); // mark u-002 as settled
|
|
310
|
+
const balances = split.getBalances(exp.id);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Payment URI (QR / UPI / SEPA)
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { JoopPaymentUriService } from 'joopjs';
|
|
319
|
+
const uri = new JoopPaymentUriService();
|
|
320
|
+
|
|
321
|
+
const upiUri = uri.generate({ scheme: 'upi', payeeVpa: 'merchant@bank', amount: 100, currency: 'INR' });
|
|
322
|
+
const parsed = uri.parse('upi://pay?pa=merchant@bank&am=100');
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Open Banking
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { JoopOpenBankingClient } from 'joopjs';
|
|
331
|
+
const ob = new JoopOpenBankingClient({ baseUrl: 'https://api.bank.com', clientId: 'my-app' });
|
|
332
|
+
|
|
333
|
+
const consent = await ob.requestConsent({ permissions: ['accounts', 'transactions'], userId: 'u-001' });
|
|
334
|
+
const accounts = await ob.getAccounts(consent.consentId);
|
|
335
|
+
const txns = await ob.getTransactions(accounts[0].accountId, { from: startTs, to: endTs });
|
|
336
|
+
const payment = await ob.initiatePayment({ debtorAccount: 'ACC-001', creditorAccount: 'ACC-002', amount: 500 });
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Chequebook
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { JoopChequebookService } from 'joopjs';
|
|
345
|
+
const cheques = new JoopChequebookService();
|
|
346
|
+
|
|
347
|
+
const book = cheques.requestChequebook({ accountId: 'ACC-001', leaves: 25 });
|
|
348
|
+
cheques.activateChequebook(book.id);
|
|
349
|
+
const leaf = cheques.issueLeaf(book.id, { payeeName: 'Alice', amount: 500, date: Date.now() });
|
|
350
|
+
cheques.markCleared(leaf.chequeNumber);
|
|
351
|
+
const stats = cheques.getStats(book.id);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## What Gets Published to npm
|
|
357
|
+
|
|
358
|
+
Controlled by `"files"` in `package.json` — acts as an allowlist. Only these two are included:
|
|
359
|
+
|
|
360
|
+
| Published to npm | Never published |
|
|
361
|
+
|-----------------|----------------|
|
|
362
|
+
| `dist/` — ESM + CJS + `.d.ts` for all 34 sub-paths | `src/` — TypeScript source |
|
|
363
|
+
| `CHANGELOG.md` — release history | `tests/` — test suite |
|
|
364
|
+
| | `scripts/` — release automation |
|
|
365
|
+
| | `playground/` — Vite demo app |
|
|
366
|
+
| | `.claude/` — Claude skills (including this file) |
|
|
367
|
+
| | `.cursor/` — Cursor rules |
|
|
368
|
+
| | `.windsurf/` — Windsurf rules |
|
|
369
|
+
| | `GEMINI.md`, `AGENTS.md` — AI tool instructions |
|
|
370
|
+
| | `tsup.config.ts`, `vitest.config.ts`, `tsconfig.json` |
|
|
371
|
+
|
|
372
|
+
Source code, AI rules, and dev tooling are **never** published to npm.
|
|
373
|
+
|
|
374
|
+
Verify the tarball contents before any publish:
|
|
375
|
+
```bash
|
|
376
|
+
npm pack --dry-run
|
|
377
|
+
```
|