@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.
- package/dist/db.d.ts +145 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +373 -0
- package/dist/db.js.map +1 -0
- package/dist/errors.d.ts +72 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +124 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +53 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +109 -0
- package/dist/logger.js.map +1 -0
- package/dist/rules-engine.d.ts +159 -0
- package/dist/rules-engine.d.ts.map +1 -0
- package/dist/rules-engine.js +375 -0
- package/dist/rules-engine.js.map +1 -0
- package/dist/rules-store.d.ts +187 -0
- package/dist/rules-store.d.ts.map +1 -0
- package/dist/rules-store.js +291 -0
- package/dist/rules-store.js.map +1 -0
- package/dist/rules-validation.d.ts +54 -0
- package/dist/rules-validation.d.ts.map +1 -0
- package/dist/rules-validation.js +110 -0
- package/dist/rules-validation.js.map +1 -0
- package/dist/stripe-client.d.ts +154 -0
- package/dist/stripe-client.d.ts.map +1 -0
- package/dist/stripe-client.js +444 -0
- package/dist/stripe-client.js.map +1 -0
- package/dist/test-utils.d.ts +55 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +91 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/tracker.d.ts +130 -0
- package/dist/tracker.d.ts.map +1 -0
- package/dist/tracker.js +196 -0
- package/dist/tracker.js.map +1 -0
- package/dist/transaction-reconciler.d.ts +30 -0
- package/dist/transaction-reconciler.d.ts.map +1 -0
- package/dist/transaction-reconciler.js +131 -0
- package/dist/transaction-reconciler.js.map +1 -0
- package/dist/types.d.ts +194 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks.d.ts +121 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +307 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +46 -0
package/dist/types.d.ts
ADDED
|
@@ -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":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,yDAAyD;IACzD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IACrE,oBAAoB,EAAE,KAAK,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gBAAgB,EAAE,cAAc,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IAExB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAG7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IAG1E,SAAS,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IAG1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,iBAAiB,GACjB,aAAa,GACb,gBAAgB,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
package/dist/types.js
ADDED
|
@@ -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"}
|
package/dist/webhooks.js
ADDED
|
@@ -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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"}
|