@tallion/sdk 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/README.md +189 -0
- package/dist/index.d.mts +193 -0
- package/dist/index.d.ts +193 -0
- package/dist/index.js +380 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +348 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthorizeModule: () => AuthorizeModule,
|
|
24
|
+
BalanceModule: () => BalanceModule,
|
|
25
|
+
PurchaseModule: () => PurchaseModule,
|
|
26
|
+
TallionError: () => TallionError,
|
|
27
|
+
Tally: () => Tally,
|
|
28
|
+
WebhooksModule: () => WebhooksModule
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
var TallionError = class extends Error {
|
|
34
|
+
constructor(status, message, code) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "TallionError";
|
|
37
|
+
this.status = status;
|
|
38
|
+
this.code = code;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/http.ts
|
|
43
|
+
var SANDBOX_URL = "https://api.sandbox.tallion.ai";
|
|
44
|
+
var PRODUCTION_URL = "https://api.tallion.ai";
|
|
45
|
+
function resolveBaseUrl(apiKey, overrideUrl) {
|
|
46
|
+
if (overrideUrl) return overrideUrl;
|
|
47
|
+
if (apiKey.startsWith("sk_live_")) return PRODUCTION_URL;
|
|
48
|
+
return SANDBOX_URL;
|
|
49
|
+
}
|
|
50
|
+
async function request(baseUrl, path, apiKey, options = {}) {
|
|
51
|
+
const url = `${baseUrl}/api${path}`;
|
|
52
|
+
const headers = {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Authorization: `Bearer ${apiKey}`,
|
|
55
|
+
...options.headers
|
|
56
|
+
};
|
|
57
|
+
const res = await fetch(url, {
|
|
58
|
+
method: options.method || "GET",
|
|
59
|
+
headers,
|
|
60
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
const body = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
64
|
+
throw new TallionError(
|
|
65
|
+
res.status,
|
|
66
|
+
body.error || "Request failed",
|
|
67
|
+
body.code
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return res.json();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/authorize.ts
|
|
74
|
+
function generateRandomString(length) {
|
|
75
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
76
|
+
const array = new Uint8Array(length);
|
|
77
|
+
crypto.getRandomValues(array);
|
|
78
|
+
return Array.from(array, (byte) => chars[byte % chars.length]).join("");
|
|
79
|
+
}
|
|
80
|
+
async function sha256(plain) {
|
|
81
|
+
const encoder = new TextEncoder();
|
|
82
|
+
const data = encoder.encode(plain);
|
|
83
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
84
|
+
return btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
85
|
+
}
|
|
86
|
+
var AuthorizeModule = class {
|
|
87
|
+
constructor(baseUrl, apiKey) {
|
|
88
|
+
this.baseUrl = baseUrl;
|
|
89
|
+
this.apiKey = apiKey;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create an authorization URL for customer consent.
|
|
93
|
+
* Returns the URL to open in a popup/browser plus the PKCE code verifier.
|
|
94
|
+
*/
|
|
95
|
+
async createUrl(options) {
|
|
96
|
+
const codeVerifier = generateRandomString(64);
|
|
97
|
+
const codeChallenge = await sha256(codeVerifier);
|
|
98
|
+
const res = await request(this.baseUrl, "/oauth/authorize", this.apiKey, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
body: {
|
|
101
|
+
customer_identifier: options.customerIdentifier,
|
|
102
|
+
redirect_url: options.redirectUrl,
|
|
103
|
+
scopes: options.scopes || ["purchase", "balance:read"],
|
|
104
|
+
code_challenge: codeChallenge,
|
|
105
|
+
code_challenge_method: options.codeChallengeMethod || "S256",
|
|
106
|
+
suggested_limits: options.suggestedLimits ? {
|
|
107
|
+
max_per_transaction: options.suggestedLimits.maxPerTransaction,
|
|
108
|
+
max_per_day: options.suggestedLimits.maxPerDay,
|
|
109
|
+
max_per_month: options.suggestedLimits.maxPerMonth,
|
|
110
|
+
require_approval_above: options.suggestedLimits.requireApprovalAbove
|
|
111
|
+
} : void 0
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
url: res.url,
|
|
116
|
+
state: res.state,
|
|
117
|
+
codeVerifier,
|
|
118
|
+
authorizationId: res.authorization_id
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Exchange an authorization code for access + refresh tokens.
|
|
123
|
+
*/
|
|
124
|
+
async exchangeCode(options) {
|
|
125
|
+
const res = await request(this.baseUrl, "/oauth/token", this.apiKey, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
body: {
|
|
128
|
+
grant_type: "authorization_code",
|
|
129
|
+
code: options.code,
|
|
130
|
+
code_verifier: options.codeVerifier
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
accessToken: res.access_token,
|
|
135
|
+
refreshToken: res.refresh_token,
|
|
136
|
+
tokenType: res.token_type,
|
|
137
|
+
expiresIn: res.expires_in,
|
|
138
|
+
customerId: res.customer_id,
|
|
139
|
+
installationId: res.installation_id
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Refresh an access token using a refresh token.
|
|
144
|
+
*/
|
|
145
|
+
async refreshToken(options) {
|
|
146
|
+
const res = await request(this.baseUrl, "/oauth/token", this.apiKey, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
body: {
|
|
149
|
+
grant_type: "refresh_token",
|
|
150
|
+
refresh_token: options.refreshToken
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
accessToken: res.access_token,
|
|
155
|
+
refreshToken: res.refresh_token,
|
|
156
|
+
tokenType: res.token_type,
|
|
157
|
+
expiresIn: res.expires_in,
|
|
158
|
+
customerId: res.customer_id,
|
|
159
|
+
installationId: res.installation_id
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Revoke an access or refresh token.
|
|
164
|
+
*/
|
|
165
|
+
async revoke(token) {
|
|
166
|
+
await request(this.baseUrl, "/oauth/revoke", this.apiKey, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
body: { token }
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/balance.ts
|
|
174
|
+
var BalanceModule = class {
|
|
175
|
+
constructor(baseUrl, apiKey) {
|
|
176
|
+
this.baseUrl = baseUrl;
|
|
177
|
+
this.apiKey = apiKey;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get wallet balance for a customer (using OAuth token).
|
|
181
|
+
*/
|
|
182
|
+
async get(customerToken, walletId) {
|
|
183
|
+
const path = walletId ? `/wallets/${walletId}/budget` : "/wallets/me";
|
|
184
|
+
const res = await request(this.baseUrl, path, customerToken, {
|
|
185
|
+
method: "GET"
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
walletId: res.wallet_id || res.id || "",
|
|
189
|
+
fundingAmount: res.funding_amount,
|
|
190
|
+
spentAmount: res.spent_amount,
|
|
191
|
+
remaining: res.remaining
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/purchase.ts
|
|
197
|
+
var PurchaseModule = class {
|
|
198
|
+
constructor(baseUrl, apiKey) {
|
|
199
|
+
this.baseUrl = baseUrl;
|
|
200
|
+
this.apiKey = apiKey;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Make a purchase using an OAuth customer token.
|
|
204
|
+
*/
|
|
205
|
+
async create(options) {
|
|
206
|
+
const res = await request(this.baseUrl, "/purchase", options.customerToken, {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: {
|
|
209
|
+
"X-Tallion-Installation": ""
|
|
210
|
+
// Resolved by OAuth token
|
|
211
|
+
},
|
|
212
|
+
body: {
|
|
213
|
+
amount: options.amount,
|
|
214
|
+
currency: options.currency || "USD",
|
|
215
|
+
wallet_id: options.walletId,
|
|
216
|
+
merchant: {
|
|
217
|
+
name: options.merchant.name,
|
|
218
|
+
mcc: options.merchant.mcc || "",
|
|
219
|
+
country: options.merchant.country || "US"
|
|
220
|
+
},
|
|
221
|
+
context: {
|
|
222
|
+
description: options.context.description,
|
|
223
|
+
category: options.context.category,
|
|
224
|
+
line_items: options.context.lineItems,
|
|
225
|
+
external_reference: options.context.externalReference,
|
|
226
|
+
refund_policy: options.context.refundPolicy,
|
|
227
|
+
metadata: options.context.metadata
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
transactionId: res.transaction_id,
|
|
233
|
+
status: res.status,
|
|
234
|
+
decision: res.decision,
|
|
235
|
+
decisionReason: res.decision_reason,
|
|
236
|
+
amount: res.amount,
|
|
237
|
+
merchantName: res.merchant_name,
|
|
238
|
+
approvalDeadline: res.approval_deadline
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Make a purchase using the legacy API key + installation ID auth.
|
|
243
|
+
*/
|
|
244
|
+
async legacyCreate(options) {
|
|
245
|
+
const res = await request(this.baseUrl, "/purchase", this.apiKey, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
headers: {
|
|
248
|
+
"X-Tallion-Installation": options.installationId
|
|
249
|
+
},
|
|
250
|
+
body: {
|
|
251
|
+
amount: options.amount,
|
|
252
|
+
merchant_name: options.merchantName,
|
|
253
|
+
merchant_mcc: options.merchantMcc || "",
|
|
254
|
+
merchant_country: options.merchantCountry || "US",
|
|
255
|
+
currency: options.currency || "USD",
|
|
256
|
+
wallet_id: options.walletId,
|
|
257
|
+
reasoning: options.reasoning
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
transactionId: res.transaction_id,
|
|
262
|
+
status: res.status,
|
|
263
|
+
decision: res.decision,
|
|
264
|
+
decisionReason: res.decision_reason,
|
|
265
|
+
amount: res.amount,
|
|
266
|
+
merchantName: res.merchant_name,
|
|
267
|
+
approvalDeadline: res.approval_deadline
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/webhooks.ts
|
|
273
|
+
var WebhooksModule = class {
|
|
274
|
+
constructor(secret) {
|
|
275
|
+
this.secret = secret;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Verify a webhook signature and parse the event.
|
|
279
|
+
* Uses Web Crypto API (works in Node 18+, Deno, Bun, Cloudflare Workers, etc.)
|
|
280
|
+
*
|
|
281
|
+
* @param body - Raw request body string
|
|
282
|
+
* @param signature - Value of X-Tally-Signature header
|
|
283
|
+
* @param tolerance - Max age in seconds (default: 300 = 5 minutes)
|
|
284
|
+
*/
|
|
285
|
+
async verify(body, signature, tolerance = 300) {
|
|
286
|
+
if (!this.secret) {
|
|
287
|
+
throw new TallionError(500, "Webhook secret not configured");
|
|
288
|
+
}
|
|
289
|
+
const parts = {};
|
|
290
|
+
for (const part of signature.split(",")) {
|
|
291
|
+
const [key2, ...val] = part.split("=");
|
|
292
|
+
if (key2 && val.length > 0) {
|
|
293
|
+
parts[key2] = val.join("=");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const timestamp = parts["t"];
|
|
297
|
+
const sig = parts["v1"];
|
|
298
|
+
if (!timestamp || !sig) {
|
|
299
|
+
throw new TallionError(400, "Invalid webhook signature format");
|
|
300
|
+
}
|
|
301
|
+
const ts = parseInt(timestamp, 10);
|
|
302
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
303
|
+
if (Math.abs(now - ts) > tolerance) {
|
|
304
|
+
throw new TallionError(400, "Webhook timestamp expired");
|
|
305
|
+
}
|
|
306
|
+
const message = `${timestamp}.${body}`;
|
|
307
|
+
const encoder = new TextEncoder();
|
|
308
|
+
const key = await crypto.subtle.importKey(
|
|
309
|
+
"raw",
|
|
310
|
+
encoder.encode(this.secret),
|
|
311
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
312
|
+
false,
|
|
313
|
+
["sign"]
|
|
314
|
+
);
|
|
315
|
+
const signatureBytes = await crypto.subtle.sign(
|
|
316
|
+
"HMAC",
|
|
317
|
+
key,
|
|
318
|
+
encoder.encode(message)
|
|
319
|
+
);
|
|
320
|
+
const computed = Array.from(new Uint8Array(signatureBytes)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
321
|
+
if (computed.length !== sig.length || !timingSafeEqual(computed, sig)) {
|
|
322
|
+
throw new TallionError(401, "Invalid webhook signature");
|
|
323
|
+
}
|
|
324
|
+
return JSON.parse(body);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
function timingSafeEqual(a, b) {
|
|
328
|
+
if (a.length !== b.length) return false;
|
|
329
|
+
let result = 0;
|
|
330
|
+
for (let i = 0; i < a.length; i++) {
|
|
331
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
332
|
+
}
|
|
333
|
+
return result === 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/tally.ts
|
|
337
|
+
var Tally = class {
|
|
338
|
+
constructor(config) {
|
|
339
|
+
this.apiKey = config.apiKey;
|
|
340
|
+
this.baseUrl = resolveBaseUrl(config.apiKey, config.baseUrl);
|
|
341
|
+
this.authorize = new AuthorizeModule(this.baseUrl, this.apiKey);
|
|
342
|
+
this.purchases = new PurchaseModule(this.baseUrl, this.apiKey);
|
|
343
|
+
this.balances = new BalanceModule(this.baseUrl, this.apiKey);
|
|
344
|
+
this.webhooks = new WebhooksModule();
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Convenience method: Make a purchase (delegates to purchases.create).
|
|
348
|
+
*/
|
|
349
|
+
async purchase(options) {
|
|
350
|
+
return this.purchases.create(options);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Convenience method: Get balance for a customer.
|
|
354
|
+
*/
|
|
355
|
+
async balance(customerToken, walletId) {
|
|
356
|
+
return this.balances.get(customerToken, walletId);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Check if this is a sandbox instance.
|
|
360
|
+
*/
|
|
361
|
+
get isSandbox() {
|
|
362
|
+
return this.apiKey.startsWith("sk_sandbox_");
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get the resolved base URL.
|
|
366
|
+
*/
|
|
367
|
+
get url() {
|
|
368
|
+
return this.baseUrl;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
372
|
+
0 && (module.exports = {
|
|
373
|
+
AuthorizeModule,
|
|
374
|
+
BalanceModule,
|
|
375
|
+
PurchaseModule,
|
|
376
|
+
TallionError,
|
|
377
|
+
Tally,
|
|
378
|
+
WebhooksModule
|
|
379
|
+
});
|
|
380
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/authorize.ts","../src/balance.ts","../src/purchase.ts","../src/webhooks.ts","../src/tally.ts"],"sourcesContent":["export { Tally } from \"./tally\";\nexport { TallionError } from \"./errors\";\nexport { AuthorizeModule } from \"./authorize\";\nexport { PurchaseModule } from \"./purchase\";\nexport { BalanceModule } from \"./balance\";\nexport { WebhooksModule } from \"./webhooks\";\n\nexport type {\n TallyConfig,\n CreateAuthUrlOptions,\n AuthUrlResult,\n ExchangeCodeOptions,\n TokenResult,\n RefreshTokenOptions,\n PurchaseOptions,\n LegacyPurchaseOptions,\n PurchaseResult,\n MerchantInfo,\n TransactionContext,\n LineItem,\n BalanceResult,\n SpendLimits,\n WebhookEvent,\n} from \"./types\";\n","export class TallionError extends Error {\n public readonly status: number;\n public readonly code?: string;\n\n constructor(status: number, message: string, code?: string) {\n super(message);\n this.name = \"TallionError\";\n this.status = status;\n this.code = code;\n }\n}\n","import { TallionError } from \"./errors\";\n\nconst SANDBOX_URL = \"https://api.sandbox.tallion.ai\";\nconst PRODUCTION_URL = \"https://api.tallion.ai\";\n\nexport function resolveBaseUrl(apiKey: string, overrideUrl?: string): string {\n if (overrideUrl) return overrideUrl;\n if (apiKey.startsWith(\"sk_live_\")) return PRODUCTION_URL;\n return SANDBOX_URL;\n}\n\nexport interface RequestOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: unknown;\n}\n\nexport async function request<T>(\n baseUrl: string,\n path: string,\n apiKey: string,\n options: RequestOptions = {},\n): Promise<T> {\n const url = `${baseUrl}/api${path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n ...options.headers,\n };\n\n const res = await fetch(url, {\n method: options.method || \"GET\",\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({ error: \"Unknown error\" }));\n throw new TallionError(\n res.status,\n body.error || \"Request failed\",\n body.code,\n );\n }\n\n return res.json() as Promise<T>;\n}\n","import { request } from \"./http\";\nimport type {\n AuthUrlResult,\n CreateAuthUrlOptions,\n ExchangeCodeOptions,\n RefreshTokenOptions,\n TokenResult,\n} from \"./types\";\n\n/** Generate a random string for PKCE */\nfunction generateRandomString(length: number): string {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~\";\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => chars[byte % chars.length]).join(\"\");\n}\n\n/** SHA-256 hash for PKCE S256 challenge */\nasync function sha256(plain: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(plain);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return btoa(String.fromCharCode(...new Uint8Array(hash)))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport class AuthorizeModule {\n constructor(\n private baseUrl: string,\n private apiKey: string,\n ) {}\n\n /**\n * Create an authorization URL for customer consent.\n * Returns the URL to open in a popup/browser plus the PKCE code verifier.\n */\n async createUrl(options: CreateAuthUrlOptions): Promise<AuthUrlResult> {\n const codeVerifier = generateRandomString(64);\n const codeChallenge = await sha256(codeVerifier);\n\n const res = await request<{\n authorization_id: string;\n url: string;\n state: string;\n }>(this.baseUrl, \"/oauth/authorize\", this.apiKey, {\n method: \"POST\",\n body: {\n customer_identifier: options.customerIdentifier,\n redirect_url: options.redirectUrl,\n scopes: options.scopes || [\"purchase\", \"balance:read\"],\n code_challenge: codeChallenge,\n code_challenge_method: options.codeChallengeMethod || \"S256\",\n suggested_limits: options.suggestedLimits\n ? {\n max_per_transaction: options.suggestedLimits.maxPerTransaction,\n max_per_day: options.suggestedLimits.maxPerDay,\n max_per_month: options.suggestedLimits.maxPerMonth,\n require_approval_above: options.suggestedLimits.requireApprovalAbove,\n }\n : undefined,\n },\n });\n\n return {\n url: res.url,\n state: res.state,\n codeVerifier,\n authorizationId: res.authorization_id,\n };\n }\n\n /**\n * Exchange an authorization code for access + refresh tokens.\n */\n async exchangeCode(options: ExchangeCodeOptions): Promise<TokenResult> {\n const res = await request<{\n access_token: string;\n refresh_token: string;\n token_type: string;\n expires_in: number;\n customer_id: string;\n installation_id: string;\n }>(this.baseUrl, \"/oauth/token\", this.apiKey, {\n method: \"POST\",\n body: {\n grant_type: \"authorization_code\",\n code: options.code,\n code_verifier: options.codeVerifier,\n },\n });\n\n return {\n accessToken: res.access_token,\n refreshToken: res.refresh_token,\n tokenType: res.token_type,\n expiresIn: res.expires_in,\n customerId: res.customer_id,\n installationId: res.installation_id,\n };\n }\n\n /**\n * Refresh an access token using a refresh token.\n */\n async refreshToken(options: RefreshTokenOptions): Promise<TokenResult> {\n const res = await request<{\n access_token: string;\n refresh_token: string;\n token_type: string;\n expires_in: number;\n customer_id: string;\n installation_id: string;\n }>(this.baseUrl, \"/oauth/token\", this.apiKey, {\n method: \"POST\",\n body: {\n grant_type: \"refresh_token\",\n refresh_token: options.refreshToken,\n },\n });\n\n return {\n accessToken: res.access_token,\n refreshToken: res.refresh_token,\n tokenType: res.token_type,\n expiresIn: res.expires_in,\n customerId: res.customer_id,\n installationId: res.installation_id,\n };\n }\n\n /**\n * Revoke an access or refresh token.\n */\n async revoke(token: string): Promise<void> {\n await request(this.baseUrl, \"/oauth/revoke\", this.apiKey, {\n method: \"POST\",\n body: { token },\n });\n }\n}\n","import { request } from \"./http\";\nimport type { BalanceResult } from \"./types\";\n\nexport class BalanceModule {\n constructor(\n private baseUrl: string,\n private apiKey: string,\n ) {}\n\n /**\n * Get wallet balance for a customer (using OAuth token).\n */\n async get(customerToken: string, walletId?: string): Promise<BalanceResult> {\n const path = walletId ? `/wallets/${walletId}/budget` : \"/wallets/me\";\n const res = await request<{\n id?: string;\n wallet_id?: string;\n funding_amount: number;\n spent_amount: number;\n remaining: number;\n }>(this.baseUrl, path, customerToken, {\n method: \"GET\",\n });\n\n return {\n walletId: res.wallet_id || res.id || \"\",\n fundingAmount: res.funding_amount,\n spentAmount: res.spent_amount,\n remaining: res.remaining,\n };\n }\n}\n","import { request } from \"./http\";\nimport type { LegacyPurchaseOptions, PurchaseOptions, PurchaseResult } from \"./types\";\n\nexport class PurchaseModule {\n constructor(\n private baseUrl: string,\n private apiKey: string,\n ) {}\n\n /**\n * Make a purchase using an OAuth customer token.\n */\n async create(options: PurchaseOptions): Promise<PurchaseResult> {\n const res = await request<{\n transaction_id: string;\n status: string;\n decision: string;\n decision_reason: string;\n amount: number;\n merchant_name: string;\n approval_deadline?: string;\n }>(this.baseUrl, \"/purchase\", options.customerToken, {\n method: \"POST\",\n headers: {\n \"X-Tallion-Installation\": \"\", // Resolved by OAuth token\n },\n body: {\n amount: options.amount,\n currency: options.currency || \"USD\",\n wallet_id: options.walletId,\n merchant: {\n name: options.merchant.name,\n mcc: options.merchant.mcc || \"\",\n country: options.merchant.country || \"US\",\n },\n context: {\n description: options.context.description,\n category: options.context.category,\n line_items: options.context.lineItems,\n external_reference: options.context.externalReference,\n refund_policy: options.context.refundPolicy,\n metadata: options.context.metadata,\n },\n },\n });\n\n return {\n transactionId: res.transaction_id,\n status: res.status as PurchaseResult[\"status\"],\n decision: res.decision,\n decisionReason: res.decision_reason,\n amount: res.amount,\n merchantName: res.merchant_name,\n approvalDeadline: res.approval_deadline,\n };\n }\n\n /**\n * Make a purchase using the legacy API key + installation ID auth.\n */\n async legacyCreate(options: LegacyPurchaseOptions): Promise<PurchaseResult> {\n const res = await request<{\n transaction_id: string;\n status: string;\n decision: string;\n decision_reason: string;\n amount: number;\n merchant_name: string;\n approval_deadline?: string;\n }>(this.baseUrl, \"/purchase\", this.apiKey, {\n method: \"POST\",\n headers: {\n \"X-Tallion-Installation\": options.installationId,\n },\n body: {\n amount: options.amount,\n merchant_name: options.merchantName,\n merchant_mcc: options.merchantMcc || \"\",\n merchant_country: options.merchantCountry || \"US\",\n currency: options.currency || \"USD\",\n wallet_id: options.walletId,\n reasoning: options.reasoning,\n },\n });\n\n return {\n transactionId: res.transaction_id,\n status: res.status as PurchaseResult[\"status\"],\n decision: res.decision,\n decisionReason: res.decision_reason,\n amount: res.amount,\n merchantName: res.merchant_name,\n approvalDeadline: res.approval_deadline,\n };\n }\n}\n","import type { WebhookEvent } from \"./types\";\nimport { TallionError } from \"./errors\";\n\nexport class WebhooksModule {\n constructor(private secret?: string) {}\n\n /**\n * Verify a webhook signature and parse the event.\n * Uses Web Crypto API (works in Node 18+, Deno, Bun, Cloudflare Workers, etc.)\n *\n * @param body - Raw request body string\n * @param signature - Value of X-Tally-Signature header\n * @param tolerance - Max age in seconds (default: 300 = 5 minutes)\n */\n async verify(\n body: string,\n signature: string,\n tolerance = 300,\n ): Promise<WebhookEvent> {\n if (!this.secret) {\n throw new TallionError(500, \"Webhook secret not configured\");\n }\n\n // Parse signature header: \"t={timestamp},v1={hex_signature}\"\n const parts: Record<string, string> = {};\n for (const part of signature.split(\",\")) {\n const [key, ...val] = part.split(\"=\");\n if (key && val.length > 0) {\n parts[key] = val.join(\"=\");\n }\n }\n\n const timestamp = parts[\"t\"];\n const sig = parts[\"v1\"];\n\n if (!timestamp || !sig) {\n throw new TallionError(400, \"Invalid webhook signature format\");\n }\n\n // Check timestamp freshness\n const ts = parseInt(timestamp, 10);\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - ts) > tolerance) {\n throw new TallionError(400, \"Webhook timestamp expired\");\n }\n\n // Compute HMAC-SHA256\n const message = `${timestamp}.${body}`;\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(this.secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n\n const signatureBytes = await crypto.subtle.sign(\n \"HMAC\",\n key,\n encoder.encode(message),\n );\n\n const computed = Array.from(new Uint8Array(signatureBytes))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n // Constant-time comparison\n if (computed.length !== sig.length || !timingSafeEqual(computed, sig)) {\n throw new TallionError(401, \"Invalid webhook signature\");\n }\n\n return JSON.parse(body) as WebhookEvent;\n }\n}\n\n/** Simple constant-time string comparison */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n}\n","import { AuthorizeModule } from \"./authorize\";\nimport { BalanceModule } from \"./balance\";\nimport { resolveBaseUrl } from \"./http\";\nimport { PurchaseModule } from \"./purchase\";\nimport { WebhooksModule } from \"./webhooks\";\nimport type { PurchaseOptions, PurchaseResult, TallyConfig } from \"./types\";\n\nexport class Tally {\n private baseUrl: string;\n private apiKey: string;\n\n /** OAuth authorization flow */\n public authorize: AuthorizeModule;\n\n /** Purchase operations */\n public purchases: PurchaseModule;\n\n /** Balance operations */\n public balances: BalanceModule;\n\n /** Webhook signature verification */\n public webhooks: WebhooksModule;\n\n constructor(config: TallyConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = resolveBaseUrl(config.apiKey, config.baseUrl);\n this.authorize = new AuthorizeModule(this.baseUrl, this.apiKey);\n this.purchases = new PurchaseModule(this.baseUrl, this.apiKey);\n this.balances = new BalanceModule(this.baseUrl, this.apiKey);\n this.webhooks = new WebhooksModule();\n }\n\n /**\n * Convenience method: Make a purchase (delegates to purchases.create).\n */\n async purchase(options: PurchaseOptions): Promise<PurchaseResult> {\n return this.purchases.create(options);\n }\n\n /**\n * Convenience method: Get balance for a customer.\n */\n async balance(customerToken: string, walletId?: string) {\n return this.balances.get(customerToken, walletId);\n }\n\n /**\n * Check if this is a sandbox instance.\n */\n get isSandbox(): boolean {\n return this.apiKey.startsWith(\"sk_sandbox_\");\n }\n\n /**\n * Get the resolved base URL.\n */\n get url(): string {\n return this.baseUrl;\n }\n}\n"],"mappings":"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","names":["key"]}
|