@agentspend/sdk 0.3.2 → 0.3.4
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/index.js +62 -6
- package/package.json +1 -1
- package/src/index.ts +65 -6
package/dist/index.js
CHANGED
|
@@ -42,6 +42,27 @@ function createAgentSpend(options) {
|
|
|
42
42
|
}
|
|
43
43
|
const platformApiBaseUrl = resolvePlatformApiBaseUrl(options.platformApiBaseUrl);
|
|
44
44
|
// -------------------------------------------------------------------
|
|
45
|
+
// Lazy service_id fetch + cache
|
|
46
|
+
// -------------------------------------------------------------------
|
|
47
|
+
let cachedServiceId = null;
|
|
48
|
+
async function getServiceId() {
|
|
49
|
+
if (cachedServiceId)
|
|
50
|
+
return cachedServiceId;
|
|
51
|
+
if (!options.serviceApiKey)
|
|
52
|
+
return null;
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetchImpl(joinUrl(platformApiBaseUrl, "/v1/service/me"), {
|
|
55
|
+
headers: { authorization: `Bearer ${options.serviceApiKey}` }
|
|
56
|
+
});
|
|
57
|
+
if (res.ok) {
|
|
58
|
+
const data = (await res.json());
|
|
59
|
+
cachedServiceId = data.id ?? null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch { /* graceful fallback */ }
|
|
63
|
+
return cachedServiceId;
|
|
64
|
+
}
|
|
65
|
+
// -------------------------------------------------------------------
|
|
45
66
|
// x402 singleton setup (Decision 9)
|
|
46
67
|
// Server-side: facilitator handles verify + settle over HTTP.
|
|
47
68
|
// No client-side EVM scheme needed — we delegate to the facilitator.
|
|
@@ -167,6 +188,10 @@ function createAgentSpend(options) {
|
|
|
167
188
|
}
|
|
168
189
|
catch (error) {
|
|
169
190
|
if (error instanceof AgentSpendChargeError) {
|
|
191
|
+
if (error.statusCode === 403) {
|
|
192
|
+
// No binding — return 402 so agent can discover service_id and bind
|
|
193
|
+
return return402Response(c, amountCents, currency);
|
|
194
|
+
}
|
|
170
195
|
if (error.statusCode === 402) {
|
|
171
196
|
return c.json({ error: "Payment required", details: error.details }, 402);
|
|
172
197
|
}
|
|
@@ -192,8 +217,15 @@ function createAgentSpend(options) {
|
|
|
192
217
|
catch {
|
|
193
218
|
return c.json({ error: "Invalid payment payload encoding" }, 400);
|
|
194
219
|
}
|
|
195
|
-
//
|
|
196
|
-
|
|
220
|
+
// Extract payTo from the payment payload's accepted requirements
|
|
221
|
+
// rather than generating a new deposit address.
|
|
222
|
+
// resolvePayToAddress() creates a fresh Stripe PaymentIntent each
|
|
223
|
+
// time, which would return a *different* address than the one in the
|
|
224
|
+
// 402 response the client signed against → facilitator verification
|
|
225
|
+
// would fail.
|
|
226
|
+
const acceptedPayTo = paymentPayload
|
|
227
|
+
.accepted?.payTo;
|
|
228
|
+
const payTo = acceptedPayTo ?? await resolvePayToAddress();
|
|
197
229
|
// Build the payment requirements that the payment should satisfy
|
|
198
230
|
const paymentRequirements = {
|
|
199
231
|
scheme: "exact",
|
|
@@ -236,6 +268,7 @@ function createAgentSpend(options) {
|
|
|
236
268
|
// return402Response — x402 Payment-Required format (Decision 8)
|
|
237
269
|
// -------------------------------------------------------------------
|
|
238
270
|
async function return402Response(c, amountCents, currency) {
|
|
271
|
+
const serviceId = await getServiceId();
|
|
239
272
|
try {
|
|
240
273
|
const payTo = await resolvePayToAddress();
|
|
241
274
|
const paymentRequirements = {
|
|
@@ -261,11 +294,34 @@ function createAgentSpend(options) {
|
|
|
261
294
|
// Set Payment-Required header (base64 encoded)
|
|
262
295
|
const headerValue = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
|
|
263
296
|
c.header("Payment-Required", headerValue);
|
|
264
|
-
return c.json({
|
|
297
|
+
return c.json({
|
|
298
|
+
error: "Payment required",
|
|
299
|
+
amount_cents: amountCents,
|
|
300
|
+
currency,
|
|
301
|
+
...(serviceId ? {
|
|
302
|
+
agentspend: {
|
|
303
|
+
service_id: serviceId,
|
|
304
|
+
amount_cents: amountCents,
|
|
305
|
+
}
|
|
306
|
+
} : {})
|
|
307
|
+
}, 402);
|
|
265
308
|
}
|
|
266
|
-
catch {
|
|
267
|
-
//
|
|
268
|
-
|
|
309
|
+
catch (error) {
|
|
310
|
+
// Log clearly so service developers can diagnose why crypto is unavailable
|
|
311
|
+
console.error("[agentspend] Failed to resolve crypto payTo address — returning card-only 402:", error instanceof Error ? error.message : error);
|
|
312
|
+
// Return card-only 402 (no Payment-Required header → crypto clients
|
|
313
|
+
// will see "Invalid payment required response")
|
|
314
|
+
return c.json({
|
|
315
|
+
error: "Payment required",
|
|
316
|
+
amount_cents: amountCents,
|
|
317
|
+
currency,
|
|
318
|
+
...(serviceId ? {
|
|
319
|
+
agentspend: {
|
|
320
|
+
service_id: serviceId,
|
|
321
|
+
amount_cents: amountCents,
|
|
322
|
+
}
|
|
323
|
+
} : {})
|
|
324
|
+
}, 402);
|
|
269
325
|
}
|
|
270
326
|
}
|
|
271
327
|
// -------------------------------------------------------------------
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -170,6 +170,26 @@ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
|
|
|
170
170
|
|
|
171
171
|
const platformApiBaseUrl = resolvePlatformApiBaseUrl(options.platformApiBaseUrl);
|
|
172
172
|
|
|
173
|
+
// -------------------------------------------------------------------
|
|
174
|
+
// Lazy service_id fetch + cache
|
|
175
|
+
// -------------------------------------------------------------------
|
|
176
|
+
let cachedServiceId: string | null = null;
|
|
177
|
+
|
|
178
|
+
async function getServiceId(): Promise<string | null> {
|
|
179
|
+
if (cachedServiceId) return cachedServiceId;
|
|
180
|
+
if (!options.serviceApiKey) return null;
|
|
181
|
+
try {
|
|
182
|
+
const res = await fetchImpl(joinUrl(platformApiBaseUrl, "/v1/service/me"), {
|
|
183
|
+
headers: { authorization: `Bearer ${options.serviceApiKey}` }
|
|
184
|
+
});
|
|
185
|
+
if (res.ok) {
|
|
186
|
+
const data = (await res.json()) as { id?: string };
|
|
187
|
+
cachedServiceId = data.id ?? null;
|
|
188
|
+
}
|
|
189
|
+
} catch { /* graceful fallback */ }
|
|
190
|
+
return cachedServiceId;
|
|
191
|
+
}
|
|
192
|
+
|
|
173
193
|
// -------------------------------------------------------------------
|
|
174
194
|
// x402 singleton setup (Decision 9)
|
|
175
195
|
// Server-side: facilitator handles verify + settle over HTTP.
|
|
@@ -336,6 +356,10 @@ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
|
|
|
336
356
|
c.set(PAYMENT_CONTEXT_KEY, paymentContext);
|
|
337
357
|
} catch (error) {
|
|
338
358
|
if (error instanceof AgentSpendChargeError) {
|
|
359
|
+
if (error.statusCode === 403) {
|
|
360
|
+
// No binding — return 402 so agent can discover service_id and bind
|
|
361
|
+
return return402Response(c, amountCents, currency);
|
|
362
|
+
}
|
|
339
363
|
if (error.statusCode === 402) {
|
|
340
364
|
return c.json({ error: "Payment required", details: error.details }, 402);
|
|
341
365
|
}
|
|
@@ -375,8 +399,15 @@ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
|
|
|
375
399
|
return c.json({ error: "Invalid payment payload encoding" }, 400);
|
|
376
400
|
}
|
|
377
401
|
|
|
378
|
-
//
|
|
379
|
-
|
|
402
|
+
// Extract payTo from the payment payload's accepted requirements
|
|
403
|
+
// rather than generating a new deposit address.
|
|
404
|
+
// resolvePayToAddress() creates a fresh Stripe PaymentIntent each
|
|
405
|
+
// time, which would return a *different* address than the one in the
|
|
406
|
+
// 402 response the client signed against → facilitator verification
|
|
407
|
+
// would fail.
|
|
408
|
+
const acceptedPayTo = (paymentPayload as unknown as { accepted?: { payTo?: string } })
|
|
409
|
+
.accepted?.payTo;
|
|
410
|
+
const payTo: string = acceptedPayTo ?? await resolvePayToAddress();
|
|
380
411
|
|
|
381
412
|
// Build the payment requirements that the payment should satisfy
|
|
382
413
|
const paymentRequirements: PaymentRequirements = {
|
|
@@ -446,6 +477,8 @@ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
|
|
|
446
477
|
amountCents: number,
|
|
447
478
|
currency: string
|
|
448
479
|
): Promise<Response> {
|
|
480
|
+
const serviceId = await getServiceId();
|
|
481
|
+
|
|
449
482
|
try {
|
|
450
483
|
const payTo = await resolvePayToAddress();
|
|
451
484
|
|
|
@@ -477,11 +510,37 @@ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
|
|
|
477
510
|
).toString("base64");
|
|
478
511
|
c.header("Payment-Required", headerValue);
|
|
479
512
|
|
|
480
|
-
return c.json({
|
|
481
|
-
|
|
482
|
-
|
|
513
|
+
return c.json({
|
|
514
|
+
error: "Payment required",
|
|
515
|
+
amount_cents: amountCents,
|
|
516
|
+
currency,
|
|
517
|
+
...(serviceId ? {
|
|
518
|
+
agentspend: {
|
|
519
|
+
service_id: serviceId,
|
|
520
|
+
amount_cents: amountCents,
|
|
521
|
+
}
|
|
522
|
+
} : {})
|
|
523
|
+
}, 402);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
// Log clearly so service developers can diagnose why crypto is unavailable
|
|
526
|
+
console.error(
|
|
527
|
+
"[agentspend] Failed to resolve crypto payTo address — returning card-only 402:",
|
|
528
|
+
error instanceof Error ? error.message : error
|
|
529
|
+
);
|
|
530
|
+
// Return card-only 402 (no Payment-Required header → crypto clients
|
|
531
|
+
// will see "Invalid payment required response")
|
|
483
532
|
return c.json(
|
|
484
|
-
{
|
|
533
|
+
{
|
|
534
|
+
error: "Payment required",
|
|
535
|
+
amount_cents: amountCents,
|
|
536
|
+
currency,
|
|
537
|
+
...(serviceId ? {
|
|
538
|
+
agentspend: {
|
|
539
|
+
service_id: serviceId,
|
|
540
|
+
amount_cents: amountCents,
|
|
541
|
+
}
|
|
542
|
+
} : {})
|
|
543
|
+
},
|
|
485
544
|
402
|
|
486
545
|
);
|
|
487
546
|
}
|