@opencard-dev/core 0.1.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 (53) hide show
  1. package/dist/db.d.ts +145 -0
  2. package/dist/db.d.ts.map +1 -0
  3. package/dist/db.js +373 -0
  4. package/dist/db.js.map +1 -0
  5. package/dist/errors.d.ts +72 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/errors.js +124 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +34 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +67 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/logger.d.ts +53 -0
  14. package/dist/logger.d.ts.map +1 -0
  15. package/dist/logger.js +109 -0
  16. package/dist/logger.js.map +1 -0
  17. package/dist/rules-engine.d.ts +159 -0
  18. package/dist/rules-engine.d.ts.map +1 -0
  19. package/dist/rules-engine.js +375 -0
  20. package/dist/rules-engine.js.map +1 -0
  21. package/dist/rules-store.d.ts +187 -0
  22. package/dist/rules-store.d.ts.map +1 -0
  23. package/dist/rules-store.js +291 -0
  24. package/dist/rules-store.js.map +1 -0
  25. package/dist/rules-validation.d.ts +54 -0
  26. package/dist/rules-validation.d.ts.map +1 -0
  27. package/dist/rules-validation.js +110 -0
  28. package/dist/rules-validation.js.map +1 -0
  29. package/dist/stripe-client.d.ts +154 -0
  30. package/dist/stripe-client.d.ts.map +1 -0
  31. package/dist/stripe-client.js +444 -0
  32. package/dist/stripe-client.js.map +1 -0
  33. package/dist/test-utils.d.ts +55 -0
  34. package/dist/test-utils.d.ts.map +1 -0
  35. package/dist/test-utils.js +91 -0
  36. package/dist/test-utils.js.map +1 -0
  37. package/dist/tracker.d.ts +130 -0
  38. package/dist/tracker.d.ts.map +1 -0
  39. package/dist/tracker.js +196 -0
  40. package/dist/tracker.js.map +1 -0
  41. package/dist/transaction-reconciler.d.ts +30 -0
  42. package/dist/transaction-reconciler.d.ts.map +1 -0
  43. package/dist/transaction-reconciler.js +131 -0
  44. package/dist/transaction-reconciler.js.map +1 -0
  45. package/dist/types.d.ts +194 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +7 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/webhooks.d.ts +121 -0
  50. package/dist/webhooks.d.ts.map +1 -0
  51. package/dist/webhooks.js +307 -0
  52. package/dist/webhooks.js.map +1 -0
  53. package/package.json +46 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * OpenCard TypeScript Types and Interfaces
3
+ * Core data structures for Stripe Issuing wrapper
4
+ */
5
+ /**
6
+ * OpenCard configuration for StripeClient initialization
7
+ */
8
+ export interface OpenCardConfig {
9
+ /** Stripe secret API key (or reads from STRIPE_SECRET_KEY env) */
10
+ secretKey?: string;
11
+ /** Default spending rules applied to new cards if not overridden */
12
+ defaultRules?: SpendRule;
13
+ /** Stripe API version override (defaults to latest stable) */
14
+ apiVersion?: string;
15
+ }
16
+ /**
17
+ * Cardholder information
18
+ */
19
+ export interface Cardholder {
20
+ id: string;
21
+ name: string;
22
+ email: string;
23
+ billing?: BillingDetails;
24
+ metadata?: Record<string, string>;
25
+ createdAt: Date;
26
+ status: 'active' | 'inactive';
27
+ }
28
+ /**
29
+ * Billing address details
30
+ */
31
+ export interface BillingDetails {
32
+ line1: string;
33
+ line2?: string;
34
+ city: string;
35
+ state: string;
36
+ postalCode: string;
37
+ country: string;
38
+ }
39
+ /**
40
+ * Options for card creation
41
+ */
42
+ export interface CardCreateOptions {
43
+ /** Agent or project name for this card */
44
+ agentName: string;
45
+ /**
46
+ * Human-readable description of what this card is for.
47
+ * Stored in Stripe card metadata as `opencard_description`.
48
+ * Helps agents pick the right card for each purchase.
49
+ * Example: "Engineering SaaS subscriptions — Vercel, AWS, Anthropic"
50
+ */
51
+ description?: string;
52
+ /**
53
+ * Custom rules for this card (overrides default).
54
+ *
55
+ * You can pass either `rules` (inline) or `ruleId` (a pre-created rule from
56
+ * the rules store). If you pass `rules` without a `ruleId`, the StripeClient
57
+ * will automatically create a rule in the store and use the returned ID.
58
+ *
59
+ * Prefer `ruleId` for production use — it keeps rule definitions out of
60
+ * Stripe metadata (where they'd be editable by anyone with Stripe access).
61
+ */
62
+ rules?: SpendRule;
63
+ /**
64
+ * ID of a pre-created rule in the rules store (e.g. "rule_018f1a2b...").
65
+ *
66
+ * This is the preferred way to attach rules to cards. The card metadata
67
+ * will store only this ID, not the full rule JSON. The webhook server
68
+ * looks up the actual rule from the store at authorization time.
69
+ *
70
+ * Create a rule ID with: `const ruleId = await rulesStore.createRule(myRule);`
71
+ */
72
+ ruleId?: string;
73
+ /** Card status: 'active' or 'inactive' */
74
+ status?: 'active' | 'inactive';
75
+ /** Custom metadata (tags, project ID, etc) */
76
+ metadata?: Record<string, string>;
77
+ /** Card replacement reason if replacing existing card */
78
+ replacement?: boolean;
79
+ /** Spending limits (alternative to rules) */
80
+ spendingLimits?: SpendingLimits;
81
+ }
82
+ /**
83
+ * Stripe spending limits format (for direct Stripe API calls)
84
+ */
85
+ export interface SpendingLimits {
86
+ intervalType: 'per_authorization' | 'daily' | 'monthly' | 'all_time';
87
+ intervalCurrencyUnit: 'usd';
88
+ amount: number;
89
+ }
90
+ /**
91
+ * Virtual card representation
92
+ */
93
+ export interface Card {
94
+ id: string;
95
+ last4: string;
96
+ brand: 'visa';
97
+ expiry: string;
98
+ status: 'active' | 'inactive' | 'canceled';
99
+ cardholderId: string;
100
+ agentName: string;
101
+ /**
102
+ * Human-readable description of what this card is for.
103
+ * Read from Stripe card metadata `opencard_description`.
104
+ * Null if no description was set at creation time.
105
+ */
106
+ description: string | null;
107
+ rules?: SpendRule;
108
+ spendingControls: SpendingLimits[];
109
+ metadata?: Record<string, string>;
110
+ createdAt: Date;
111
+ replacedAt?: Date;
112
+ }
113
+ /**
114
+ * Spend rule: defines what an agent card can and cannot do
115
+ */
116
+ export interface SpendRule {
117
+ requiresApproval?: boolean;
118
+ approvalThreshold?: number;
119
+ allowedCategories?: string[];
120
+ blockedCategories?: string[];
121
+ maxPerTransaction?: number;
122
+ dailyLimit?: number;
123
+ monthlyLimit?: number;
124
+ spendingInterval?: 'per_authorization' | 'daily' | 'monthly' | 'all_time';
125
+ onFailure?: 'decline' | 'alert' | 'pause';
126
+ name?: string;
127
+ description?: string;
128
+ }
129
+ /**
130
+ * Pre-built spend rule templates for common patterns
131
+ */
132
+ export type SpendRuleTemplate = 'balanced' | 'category_locked' | 'daily_limit' | 'approval_gated';
133
+ /**
134
+ * Transaction representation
135
+ */
136
+ export interface Transaction {
137
+ id: string;
138
+ cardId: string;
139
+ amount: number;
140
+ currency: string;
141
+ merchant: {
142
+ name: string;
143
+ category: string;
144
+ city?: string;
145
+ country?: string;
146
+ };
147
+ status: 'approved' | 'declined' | 'pending' | 'captured' | 'reversed';
148
+ declineReason?: string;
149
+ createdAt: Date;
150
+ metadata?: Record<string, string>;
151
+ }
152
+ /**
153
+ * Authorization event (pending transaction waiting for approval/decline)
154
+ */
155
+ export interface Authorization {
156
+ id: string;
157
+ cardId: string;
158
+ amount: number;
159
+ currency: string;
160
+ merchant: {
161
+ name: string;
162
+ category: string;
163
+ };
164
+ createdAt: Date;
165
+ status: 'pending' | 'approved' | 'declined';
166
+ }
167
+ /**
168
+ * Card balance snapshot
169
+ */
170
+ export interface CardBalance {
171
+ available: number;
172
+ reserved: number;
173
+ spent: number;
174
+ asOf: Date;
175
+ }
176
+ /**
177
+ * Transaction query options
178
+ */
179
+ export interface TransactionQueryOptions {
180
+ limit?: number;
181
+ offset?: number;
182
+ startDate?: Date;
183
+ endDate?: Date;
184
+ status?: Transaction['status'];
185
+ }
186
+ /**
187
+ * Configuration for Stripe client operations
188
+ */
189
+ export interface StripeOperationConfig {
190
+ idempotencyKey?: string;
191
+ timeout?: number;
192
+ retryCount?: number;
193
+ }
194
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * OpenCard TypeScript Types and Interfaces
4
+ * Core data structures for Stripe Issuing wrapper
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Webhook Event Handlers
3
+ * ======================
4
+ * Stripe communicates real-time card activity to us via webhooks — HTTP POST
5
+ * requests that Stripe sends to our server whenever something happens.
6
+ *
7
+ * The most critical event is `issuing_authorization.request`:
8
+ * - Stripe sends it SYNCHRONOUSLY when a card is swiped.
9
+ * - We have ~2 seconds to respond with approve or decline.
10
+ * - If we don't respond in time, Stripe declines by default.
11
+ *
12
+ * Other events (created, updated) are fire-and-forget notifications we use
13
+ * for logging and bookkeeping. They don't require a synchronous decision.
14
+ *
15
+ * ─── Stripe Issuing event flow ──────────────────────────────────────────────
16
+ *
17
+ * Card swipe → Stripe → POST /webhooks/stripe (issuing_authorization.request)
18
+ * ↓
19
+ * evaluateAuthorization()
20
+ * ↓
21
+ * HTTP 200 { approved: true/false }
22
+ * ↓
23
+ * Stripe approves/declines at POS terminal
24
+ *
25
+ * ─────────────────────────────────────────────────────────────────────────────
26
+ *
27
+ * Reference: https://stripe.com/docs/issuing/controls/real-time-authorizations
28
+ */
29
+ import Stripe from 'stripe';
30
+ import { SpendRule } from './types';
31
+ import { TransactionReconciler } from './transaction-reconciler';
32
+ /**
33
+ * Inject the reconciler instance (called at server startup, after DB init).
34
+ * If never called, reconciliation is skipped silently.
35
+ */
36
+ export declare function setReconciler(r: TransactionReconciler): void;
37
+ /**
38
+ * Get the current reconciler. Returns null if not initialized.
39
+ */
40
+ export declare function getReconciler(): TransactionReconciler | null;
41
+ /**
42
+ * Returned by handleAuthorizationRequest.
43
+ * The webhook server uses `approved` to set the HTTP response body,
44
+ * and `reason` is logged for debugging.
45
+ */
46
+ export interface AuthorizationResult {
47
+ approved: boolean;
48
+ reason: string;
49
+ }
50
+ /**
51
+ * Looks up the SpendRules that apply to a given card.
52
+ *
53
+ * New behavior (Phase 2):
54
+ * 1. Look for `opencard_rule_id` in card metadata
55
+ * 2. Look up the rule from our rules store using that ID
56
+ * 3. If lookup fails for any reason, return DEFAULT_DENY_RULE
57
+ *
58
+ * Backward compatibility:
59
+ * - If `opencard_rule_id` is not found, check for legacy `opencard_rules` JSON
60
+ * in metadata. If found, parse and use it (but log a deprecation warning).
61
+ * - If neither is found, return DEFAULT_DENY_RULE (previously returned `{}`)
62
+ *
63
+ * @param card - The Stripe Issuing.Card object from the authorization event
64
+ * @returns Resolved SpendRule — never null/undefined, always falls back to DEFAULT_DENY_RULE
65
+ */
66
+ export declare function getRulesForCard(card: Stripe.Issuing.Card): Promise<SpendRule>;
67
+ /**
68
+ * Handles `issuing_authorization.request`
69
+ * ─────────────────────────────────────────
70
+ * This is the real-time authorization handler — the most important webhook.
71
+ * Stripe calls this SYNCHRONOUSLY when a card is presented at a merchant.
72
+ * We must respond within ~2 seconds with { approved: true } or { approved: false }.
73
+ *
74
+ * What we do:
75
+ * 1. Extract the authorization details (card, amount, merchant category)
76
+ * 2. Look up the rules for this card
77
+ * 3. Run the rules engine against the current authorization + spend history
78
+ * 4. Return the decision
79
+ * 5. If approved, record the spend so future checks see up-to-date totals
80
+ *
81
+ * @param authorization - The Stripe Issuing.Authorization object
82
+ * @returns { approved, reason } — fed directly into the HTTP response
83
+ */
84
+ export declare function handleAuthorizationRequest(authorization: Stripe.Issuing.Authorization): Promise<AuthorizationResult>;
85
+ /**
86
+ * Handles `issuing_authorization.created`
87
+ * ─────────────────────────────────────────
88
+ * Fired after an authorization is definitively approved or declined.
89
+ * This is NOT the real-time request — it's a notification after the fact.
90
+ *
91
+ * Use this for logging, audit trails, and alerting. We don't make
92
+ * approve/decline decisions here.
93
+ *
94
+ * @param authorization - The completed Stripe Issuing.Authorization
95
+ */
96
+ export declare function handleAuthorizationCreated(authorization: Stripe.Issuing.Authorization): void;
97
+ /**
98
+ * Handles `issuing_transaction.created`
99
+ * ───────────────────────────────────────
100
+ * Fired when a transaction is fully captured (i.e., the merchant actually
101
+ * settled the charge). This happens after the authorization, sometimes days
102
+ * later (e.g., hotels that pre-auth then capture at checkout).
103
+ *
104
+ * The amount here may differ from the authorization amount — for example,
105
+ * a restaurant tip added after auth.
106
+ *
107
+ * Phase 2 will reconcile these with the tracker totals.
108
+ *
109
+ * @param transaction - The Stripe Issuing.Transaction
110
+ */
111
+ export declare function handleTransactionCreated(transaction: Stripe.Issuing.Transaction): void;
112
+ /**
113
+ * Handles `issuing_card.updated`
114
+ * ─────────────────────────────────
115
+ * Fired when a card's status, spending controls, or metadata changes.
116
+ * We log status changes so operators can see when cards are paused/resumed.
117
+ *
118
+ * @param card - The updated Stripe Issuing.Card
119
+ */
120
+ export declare function handleCardUpdated(card: Stripe.Issuing.Card): void;
121
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAKpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AASjE;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAE5D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,qBAAqB,GAAG,IAAI,CAE5D;AAID;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AA2BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAyEnF;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,0BAA0B,CAC9C,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,aAAa,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CA6D9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,aAAa,GAC1C,IAAI,CAuBN;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,GACtC,IAAI,CAoBN;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAEjE"}
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ /**
3
+ * Webhook Event Handlers
4
+ * ======================
5
+ * Stripe communicates real-time card activity to us via webhooks — HTTP POST
6
+ * requests that Stripe sends to our server whenever something happens.
7
+ *
8
+ * The most critical event is `issuing_authorization.request`:
9
+ * - Stripe sends it SYNCHRONOUSLY when a card is swiped.
10
+ * - We have ~2 seconds to respond with approve or decline.
11
+ * - If we don't respond in time, Stripe declines by default.
12
+ *
13
+ * Other events (created, updated) are fire-and-forget notifications we use
14
+ * for logging and bookkeeping. They don't require a synchronous decision.
15
+ *
16
+ * ─── Stripe Issuing event flow ──────────────────────────────────────────────
17
+ *
18
+ * Card swipe → Stripe → POST /webhooks/stripe (issuing_authorization.request)
19
+ * ↓
20
+ * evaluateAuthorization()
21
+ * ↓
22
+ * HTTP 200 { approved: true/false }
23
+ * ↓
24
+ * Stripe approves/declines at POS terminal
25
+ *
26
+ * ─────────────────────────────────────────────────────────────────────────────
27
+ *
28
+ * Reference: https://stripe.com/docs/issuing/controls/real-time-authorizations
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.setReconciler = setReconciler;
32
+ exports.getReconciler = getReconciler;
33
+ exports.getRulesForCard = getRulesForCard;
34
+ exports.handleAuthorizationRequest = handleAuthorizationRequest;
35
+ exports.handleAuthorizationCreated = handleAuthorizationCreated;
36
+ exports.handleTransactionCreated = handleTransactionCreated;
37
+ exports.handleCardUpdated = handleCardUpdated;
38
+ const rules_engine_1 = require("./rules-engine");
39
+ const tracker_1 = require("./tracker");
40
+ const rules_store_1 = require("./rules-store");
41
+ const rules_validation_1 = require("./rules-validation");
42
+ const logger_1 = require("./logger");
43
+ const logger = (0, logger_1.getLogger)('webhooks');
44
+ // Module-level reconciler instance, used when no reconciler is injected.
45
+ // Replaced at startup via setReconciler() for the live webhook server.
46
+ let _reconciler = null;
47
+ /**
48
+ * Inject the reconciler instance (called at server startup, after DB init).
49
+ * If never called, reconciliation is skipped silently.
50
+ */
51
+ function setReconciler(r) {
52
+ _reconciler = r;
53
+ }
54
+ /**
55
+ * Get the current reconciler. Returns null if not initialized.
56
+ */
57
+ function getReconciler() {
58
+ return _reconciler;
59
+ }
60
+ // ─── Rule resolver ─────────────────────────────────────────────────────────
61
+ /**
62
+ * The default-deny rule used when a card has no valid rule ID or the rule lookup fails.
63
+ *
64
+ * ─── Why default-deny? ────────────────────────────────────────────────────────
65
+ * If a card's rule ID is missing or the lookup fails (e.g., rule was deleted,
66
+ * rules file is corrupted, rules store is unavailable), we want to DECLINE,
67
+ * not allow. The alternative — allowing all transactions when rules are missing
68
+ * — would mean a configuration error silently removes all spending controls.
69
+ * That's much worse than being overly restrictive.
70
+ *
71
+ * An operator who sees unexpected declines will investigate. An operator who
72
+ * sees unexpected approvals might not notice until damage is done.
73
+ */
74
+ const DEFAULT_DENY_RULE = {
75
+ // maxPerTransaction: 0 would be rejected by validation, so we use 1 cent.
76
+ // The rules engine will decline any transaction over $0.01.
77
+ // More importantly, we set onFailure: 'decline' explicitly.
78
+ onFailure: 'decline',
79
+ maxPerTransaction: 1, // 1 cent — effectively declines everything
80
+ name: 'default-deny',
81
+ description: 'Default deny rule — applied when no valid rule is found for this card',
82
+ };
83
+ /**
84
+ * Looks up the SpendRules that apply to a given card.
85
+ *
86
+ * New behavior (Phase 2):
87
+ * 1. Look for `opencard_rule_id` in card metadata
88
+ * 2. Look up the rule from our rules store using that ID
89
+ * 3. If lookup fails for any reason, return DEFAULT_DENY_RULE
90
+ *
91
+ * Backward compatibility:
92
+ * - If `opencard_rule_id` is not found, check for legacy `opencard_rules` JSON
93
+ * in metadata. If found, parse and use it (but log a deprecation warning).
94
+ * - If neither is found, return DEFAULT_DENY_RULE (previously returned `{}`)
95
+ *
96
+ * @param card - The Stripe Issuing.Card object from the authorization event
97
+ * @returns Resolved SpendRule — never null/undefined, always falls back to DEFAULT_DENY_RULE
98
+ */
99
+ async function getRulesForCard(card) {
100
+ // card.metadata is a Record<string, string> — all values are strings.
101
+ const metadata = card.metadata ?? {};
102
+ // ── Case 1: New-style — look up by rule ID ──────────────────────────────────
103
+ const ruleId = metadata.opencard_rule_id;
104
+ if (ruleId) {
105
+ try {
106
+ const rule = await rules_store_1.rulesStore.getRule(ruleId);
107
+ if (rule) {
108
+ // Happy path: rule found in store
109
+ return rule;
110
+ }
111
+ // Rule ID exists in metadata but was not found in the store.
112
+ // This can happen if a rule was deleted without updating the card.
113
+ // Fall back to default-deny and log clearly so operators know.
114
+ console.error(`[Webhooks] ⚠️ Card ${card.id} references rule ID "${ruleId}" but that rule was not found in the store. ` +
115
+ `Did you delete the rule? Falling back to DEFAULT DENY — this card will decline all transactions.`);
116
+ return DEFAULT_DENY_RULE;
117
+ }
118
+ catch (err) {
119
+ // Unexpected error during rules store lookup (e.g., file system error)
120
+ console.error(`[Webhooks] ⚠️ Failed to look up rule "${ruleId}" for card ${card.id}: ${String(err)}. ` +
121
+ `Falling back to DEFAULT DENY.`);
122
+ return DEFAULT_DENY_RULE;
123
+ }
124
+ }
125
+ // ── Case 2: Backward compat — legacy opencard_rules JSON in metadata ────────
126
+ const legacyRaw = metadata.opencard_rules;
127
+ if (legacyRaw) {
128
+ console.warn(`[Webhooks] ⚠️ DEPRECATED: Card ${card.id} uses legacy opencard_rules metadata (full rule JSON in Stripe). ` +
129
+ `This is a security risk. Migrate this card to use opencard_rule_id with the rules store. ` +
130
+ `See docs/rules-engine.md for migration instructions.`);
131
+ try {
132
+ const parsed = JSON.parse(legacyRaw);
133
+ // Validate the parsed rule — don't trust raw JSON from Stripe metadata.
134
+ // This closes the same security hole the rules store was built to fix:
135
+ // without validation, anyone with Stripe dashboard access could inject
136
+ // malformed or malicious rules (e.g. negative limits that always pass).
137
+ const validation = (0, rules_validation_1.validateRule)(parsed);
138
+ if (!validation.valid) {
139
+ console.error(`[Webhooks] ⚠️ Legacy opencard_rules for card ${card.id} failed validation: ` +
140
+ `${validation.errors.join(', ')}. Falling back to DEFAULT DENY.`);
141
+ return DEFAULT_DENY_RULE;
142
+ }
143
+ return parsed;
144
+ }
145
+ catch {
146
+ console.error(`[Webhooks] ⚠️ Failed to parse legacy opencard_rules for card ${card.id}. ` +
147
+ `Falling back to DEFAULT DENY.`);
148
+ return DEFAULT_DENY_RULE;
149
+ }
150
+ }
151
+ // ── Case 3: No rules at all ──────────────────────────────────────────────────
152
+ // Previously we returned {} here, which allowed everything. Now we default-deny.
153
+ console.error(`[Webhooks] ⚠️ Card ${card.id} has no opencard_rule_id or opencard_rules metadata. ` +
154
+ `Falling back to DEFAULT DENY — this card will decline all transactions. ` +
155
+ `Did you forget to attach a rule when creating the card?`);
156
+ return DEFAULT_DENY_RULE;
157
+ }
158
+ // ─── Event handlers ───────────────────────────────────────────────────────────
159
+ /**
160
+ * Handles `issuing_authorization.request`
161
+ * ─────────────────────────────────────────
162
+ * This is the real-time authorization handler — the most important webhook.
163
+ * Stripe calls this SYNCHRONOUSLY when a card is presented at a merchant.
164
+ * We must respond within ~2 seconds with { approved: true } or { approved: false }.
165
+ *
166
+ * What we do:
167
+ * 1. Extract the authorization details (card, amount, merchant category)
168
+ * 2. Look up the rules for this card
169
+ * 3. Run the rules engine against the current authorization + spend history
170
+ * 4. Return the decision
171
+ * 5. If approved, record the spend so future checks see up-to-date totals
172
+ *
173
+ * @param authorization - The Stripe Issuing.Authorization object
174
+ * @returns { approved, reason } — fed directly into the HTTP response
175
+ */
176
+ async function handleAuthorizationRequest(authorization) {
177
+ const cardId = typeof authorization.card === 'string'
178
+ ? authorization.card
179
+ : authorization.card.id;
180
+ // Log enough context to debug any issues after the fact.
181
+ console.log(`[Auth] Request ${authorization.id} | card=${cardId} | amount=${authorization.amount}¢ | merchant=${authorization.merchant_data?.name} | mcc=${authorization.merchant_data?.category}`);
182
+ // ── Get the card's rules ─────────────────────────────────────────────
183
+ // The card object is embedded in the authorization event — no extra API
184
+ // call needed. We look up the rules from our rules store using the rule ID
185
+ // stored in card metadata. If the rule can't be found, we get default-deny.
186
+ const card = authorization.card;
187
+ const rules = await getRulesForCard(card);
188
+ // ── Pull current spend totals ────────────────────────────────────────
189
+ // The tracker holds running daily/monthly totals so the rules engine can
190
+ // check "would this push us over the limit?"
191
+ const dailySpend = tracker_1.tracker.getDailySpend(cardId);
192
+ const monthlySpend = tracker_1.tracker.getMonthlySpend(cardId);
193
+ // ── Evaluate the authorization ───────────────────────────────────────
194
+ // The rules engine checks all applicable rules (category, amount limit,
195
+ // daily budget, monthly budget, confidence) and returns a decision.
196
+ const decision = (0, rules_engine_1.evaluateAuthorization)(authorization, rules, {
197
+ dailySpend,
198
+ monthlySpend,
199
+ });
200
+ // ── Record approved spend immediately ───────────────────────────────
201
+ // If approved, we immediately add this amount to the running totals.
202
+ // This prevents a race condition where two simultaneous authorizations
203
+ // both see the pre-approval total and both get approved even though
204
+ // together they exceed the limit.
205
+ //
206
+ // Note: Stripe may later reverse the transaction (e.g. if the merchant
207
+ // cancels). Phase 2 will handle reversals. For now we're conservative.
208
+ if (decision.approved) {
209
+ tracker_1.tracker.addTransaction(cardId, authorization.amount);
210
+ console.log(`[Auth] ✓ APPROVED ${authorization.id} — ${decision.reason}`);
211
+ }
212
+ else {
213
+ console.log(`[Auth] ✗ DECLINED ${authorization.id} — ${decision.reason}`);
214
+ }
215
+ // ── Persist to SQLite (non-blocking) ────────────────────────────────────
216
+ // DB errors must NEVER block the 2s SLA. Wrap in try/catch, log failures.
217
+ if (_reconciler) {
218
+ try {
219
+ _reconciler.handleAuthorizationRequest(authorization, decision.reason);
220
+ }
221
+ catch (err) {
222
+ logger.error('Failed to persist authorization request to DB — decision not affected', {
223
+ stripe_id: authorization.id,
224
+ error: err instanceof Error ? err.message : String(err),
225
+ });
226
+ }
227
+ }
228
+ return {
229
+ approved: decision.approved,
230
+ reason: decision.reason,
231
+ };
232
+ }
233
+ /**
234
+ * Handles `issuing_authorization.created`
235
+ * ─────────────────────────────────────────
236
+ * Fired after an authorization is definitively approved or declined.
237
+ * This is NOT the real-time request — it's a notification after the fact.
238
+ *
239
+ * Use this for logging, audit trails, and alerting. We don't make
240
+ * approve/decline decisions here.
241
+ *
242
+ * @param authorization - The completed Stripe Issuing.Authorization
243
+ */
244
+ function handleAuthorizationCreated(authorization) {
245
+ const cardId = typeof authorization.card === 'string'
246
+ ? authorization.card
247
+ : authorization.card.id;
248
+ // Stripe stores the final approval status on the `approved` field.
249
+ const status = authorization.approved ? 'approved' : 'declined';
250
+ console.log(`[Auth] Created: ${authorization.id} | status=${status} | card=${cardId} | amount=${authorization.amount}¢ | merchant=${authorization.merchant_data?.name}`);
251
+ // Persist status update to SQLite
252
+ if (_reconciler) {
253
+ try {
254
+ _reconciler.handleAuthorizationCreated(authorization);
255
+ }
256
+ catch (err) {
257
+ logger.error('Failed to persist authorization.created to DB', {
258
+ stripe_id: authorization.id,
259
+ error: err instanceof Error ? err.message : String(err),
260
+ });
261
+ }
262
+ }
263
+ }
264
+ /**
265
+ * Handles `issuing_transaction.created`
266
+ * ───────────────────────────────────────
267
+ * Fired when a transaction is fully captured (i.e., the merchant actually
268
+ * settled the charge). This happens after the authorization, sometimes days
269
+ * later (e.g., hotels that pre-auth then capture at checkout).
270
+ *
271
+ * The amount here may differ from the authorization amount — for example,
272
+ * a restaurant tip added after auth.
273
+ *
274
+ * Phase 2 will reconcile these with the tracker totals.
275
+ *
276
+ * @param transaction - The Stripe Issuing.Transaction
277
+ */
278
+ function handleTransactionCreated(transaction) {
279
+ const cardId = typeof transaction.card === 'string'
280
+ ? transaction.card
281
+ : transaction.card?.id || 'unknown';
282
+ console.log(`[Tx] Created: ${transaction.id} | card=${cardId} | amount=${transaction.amount}¢ | merchant=${transaction.merchant_data?.name} | category=${transaction.merchant_data?.category}`);
283
+ // Persist transaction to SQLite
284
+ if (_reconciler) {
285
+ try {
286
+ _reconciler.handleTransactionCreated(transaction);
287
+ }
288
+ catch (err) {
289
+ logger.error('Failed to persist transaction.created to DB', {
290
+ stripe_id: transaction.id,
291
+ error: err instanceof Error ? err.message : String(err),
292
+ });
293
+ }
294
+ }
295
+ }
296
+ /**
297
+ * Handles `issuing_card.updated`
298
+ * ─────────────────────────────────
299
+ * Fired when a card's status, spending controls, or metadata changes.
300
+ * We log status changes so operators can see when cards are paused/resumed.
301
+ *
302
+ * @param card - The updated Stripe Issuing.Card
303
+ */
304
+ function handleCardUpdated(card) {
305
+ console.log(`[Card] Updated: ${card.id} | status=${card.status}`);
306
+ }
307
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}