@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 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
- // Resolve the payTo address for verification context
196
- const payTo = await resolvePayToAddress();
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({ error: "Payment required", amount_cents: amountCents, currency }, 402);
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
- // If we can't resolve a payTo address, return a plain 402
268
- return c.json({ error: "Payment required", amount_cents: amountCents, currency }, 402);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentspend/sdk",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "publishConfig": {
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
- // Resolve the payTo address for verification context
379
- const payTo = await resolvePayToAddress();
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({ error: "Payment required", amount_cents: amountCents, currency }, 402);
481
- } catch {
482
- // If we can't resolve a payTo address, return a plain 402
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
- { error: "Payment required", amount_cents: amountCents, currency },
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
  }