@agentspend/sdk 0.3.8 → 0.3.9

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.
@@ -0,0 +1,87 @@
1
+ export function toCardId(input: unknown): string | null {
2
+ if (typeof input !== "string") {
3
+ return null;
4
+ }
5
+ const trimmed = input.trim();
6
+ if (!trimmed.startsWith("card_")) {
7
+ return null;
8
+ }
9
+ return trimmed;
10
+ }
11
+
12
+ export function joinUrl(base: string, path: string): string {
13
+ const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
14
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
15
+ return `${normalizedBase}${normalizedPath}`;
16
+ }
17
+
18
+ export function bestEffortIdempotencyKey(): string {
19
+ const uuid = globalThis.crypto?.randomUUID?.();
20
+ if (uuid) {
21
+ return uuid;
22
+ }
23
+ return `auto_${Date.now()}_${Math.random().toString(16).slice(2)}`;
24
+ }
25
+
26
+ export function toStringMetadata(input: unknown): Record<string, string> {
27
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
28
+ return {};
29
+ }
30
+
31
+ const result: Record<string, string> = {};
32
+ for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
33
+ if (typeof value === "string") {
34
+ result[key] = value;
35
+ } else if (typeof value === "number" && Number.isFinite(value)) {
36
+ result[key] = String(value);
37
+ } else if (typeof value === "boolean") {
38
+ result[key] = value ? "true" : "false";
39
+ }
40
+ }
41
+ return result;
42
+ }
43
+
44
+ const DEFAULT_PLATFORM_API_BASE_URL = "https://api.agentspend.co";
45
+
46
+ export function resolvePlatformApiBaseUrl(explicitBaseUrl: string | undefined): string {
47
+ if (explicitBaseUrl && explicitBaseUrl.trim().length > 0) {
48
+ return explicitBaseUrl.trim();
49
+ }
50
+
51
+ const envValue =
52
+ typeof process !== "undefined" && process.env ? process.env.AGENTSPEND_API_URL : undefined;
53
+
54
+ if (typeof envValue === "string" && envValue.trim().length > 0) {
55
+ return envValue.trim();
56
+ }
57
+
58
+ return DEFAULT_PLATFORM_API_BASE_URL;
59
+ }
60
+
61
+ export function resolveAmount(amount: number | string | ((body: unknown) => number), body: unknown): number {
62
+ if (typeof amount === "number") {
63
+ return amount;
64
+ } else if (typeof amount === "string") {
65
+ const raw = (body as Record<string, unknown>)?.[amount];
66
+ return typeof raw === "number" ? raw : 0;
67
+ } else {
68
+ return amount(body);
69
+ }
70
+ }
71
+
72
+ export function extractCardId(headers: Record<string, string | undefined>, body: unknown): string | null {
73
+ const cardIdFromHeader = headers["x-card-id"];
74
+ let cardId = cardIdFromHeader ? toCardId(cardIdFromHeader) : null;
75
+ if (!cardId) {
76
+ const bodyCardId =
77
+ typeof (body as { card_id?: unknown })?.card_id === "string"
78
+ ? (body as { card_id: string }).card_id
79
+ : null;
80
+ cardId = toCardId(bodyCardId);
81
+ }
82
+ return cardId;
83
+ }
84
+
85
+ export function extractPaymentHeader(headers: Record<string, string | undefined>): string | undefined {
86
+ return headers["payment-signature"] ?? headers["x-payment"] ?? undefined;
87
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export * from "./error.js";
3
+ export * from "./helpers.js";
4
+ export { createAgentSpendClient } from "./client.js";
5
+ export type { AgentSpendClient } from "./client.js";
@@ -0,0 +1,104 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Types (inlined from @agentspend/types to avoid cross-repo publish)
3
+ // ---------------------------------------------------------------------------
4
+
5
+ export interface ChargeRequest {
6
+ card_id: string;
7
+ amount_cents: number;
8
+ currency?: string;
9
+ description?: string;
10
+ metadata?: Record<string, string>;
11
+ idempotency_key?: string;
12
+ }
13
+
14
+ export interface ChargeResponse {
15
+ charged: true;
16
+ card_id: string;
17
+ amount_cents: number;
18
+ currency: string;
19
+ remaining_limit_cents: number;
20
+ stripe_payment_intent_id: string;
21
+ stripe_charge_id: string;
22
+ charge_attempt_id: string;
23
+ }
24
+
25
+ export interface ErrorResponse {
26
+ error: string;
27
+ }
28
+
29
+ export type PaymentMethod = "card" | "crypto";
30
+
31
+ export interface PaywallPaymentContext {
32
+ method: PaymentMethod;
33
+ amount_cents: number;
34
+ currency: string;
35
+ card_id?: string;
36
+ remaining_limit_cents?: number;
37
+ transaction_hash?: string;
38
+ payer_address?: string;
39
+ network?: string;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Options
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export interface AgentSpendOptions {
47
+ /**
48
+ * Base URL for the AgentSpend Platform API.
49
+ *
50
+ * If omitted, the SDK will use `process.env.AGENTSPEND_API_URL` when available,
51
+ * otherwise it falls back to the hosted default.
52
+ */
53
+ platformApiBaseUrl?: string;
54
+ /** Service API key. Optional — crypto-only services don't need one. */
55
+ serviceApiKey?: string;
56
+ fetchImpl?: typeof fetch;
57
+ /** Crypto / x402 configuration. */
58
+ crypto?: {
59
+ /** Static payTo address for crypto-only services. */
60
+ receiverAddress?: string;
61
+ /** Chain identifier. Default: "eip155:8453" (Base). */
62
+ network?: string;
63
+ /** x402 facilitator URL. Default: "https://x402.org/facilitator". */
64
+ facilitatorUrl?: string;
65
+ };
66
+ }
67
+
68
+ export interface ChargeOptions {
69
+ amount_cents: number;
70
+ currency?: string;
71
+ description?: string;
72
+ metadata?: Record<string, string>;
73
+ idempotency_key?: string;
74
+ }
75
+
76
+ export interface PaywallOptions {
77
+ /**
78
+ * Amount in cents.
79
+ * - number: fixed price (e.g. 500 = $5.00)
80
+ * - string: body field name to read amount from (e.g. "amount_cents")
81
+ * - function: custom dynamic pricing (body: unknown) => number
82
+ */
83
+ amount: number | string | ((body: unknown) => number);
84
+ currency?: string;
85
+ description?: string;
86
+ metadata?: (body: unknown) => Record<string, unknown>;
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Framework-agnostic request / result types
91
+ // ---------------------------------------------------------------------------
92
+
93
+ export interface PaywallRequest {
94
+ url: string;
95
+ method: string;
96
+ headers: Record<string, string | undefined>;
97
+ body: unknown;
98
+ }
99
+
100
+ export type PaywallResult =
101
+ | { outcome: "charged"; paymentContext: PaywallPaymentContext }
102
+ | { outcome: "crypto_paid"; paymentContext: PaywallPaymentContext }
103
+ | { outcome: "payment_required"; statusCode: 402; body: Record<string, unknown>; headers: Record<string, string> }
104
+ | { outcome: "error"; statusCode: number; body: Record<string, unknown> };
@@ -0,0 +1,101 @@
1
+ import { createAgentSpendClient } from "../core/client.js";
2
+ import type {
3
+ AgentSpendOptions,
4
+ ChargeOptions,
5
+ ChargeResponse,
6
+ PaywallOptions,
7
+ PaywallPaymentContext,
8
+ PaywallRequest
9
+ } from "../core/types.js";
10
+
11
+ export * from "../core/index.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Express type abstractions (avoid hard dependency on @types/express)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ interface ExpressRequest {
18
+ body: unknown;
19
+ url: string;
20
+ method: string;
21
+ get(name: string): string | undefined;
22
+ header(name: string): string | undefined;
23
+ paymentContext?: PaywallPaymentContext;
24
+ }
25
+
26
+ interface ExpressResponse {
27
+ status(code: number): ExpressResponse;
28
+ set(name: string, value: string): ExpressResponse;
29
+ json(body: unknown): void;
30
+ }
31
+
32
+ type NextFunction = (err?: unknown) => void;
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Payment context helper
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export function getPaymentContext(req: ExpressRequest): PaywallPaymentContext | null {
39
+ return req.paymentContext ?? null;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Public interface
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export interface AgentSpend {
47
+ charge(cardId: string, opts: ChargeOptions): Promise<ChargeResponse>;
48
+ paywall(opts: PaywallOptions): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Factory
53
+ // ---------------------------------------------------------------------------
54
+
55
+ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
56
+ const client = createAgentSpendClient(options);
57
+
58
+ function paywall(opts: PaywallOptions) {
59
+ return function paywallMiddleware(
60
+ req: ExpressRequest,
61
+ res: ExpressResponse,
62
+ next: NextFunction
63
+ ): void {
64
+ const headerGet = (name: string) => req.get?.(name) ?? req.header?.(name);
65
+
66
+ const request: PaywallRequest = {
67
+ url: req.url,
68
+ method: req.method,
69
+ headers: {
70
+ "x-card-id": headerGet("x-card-id"),
71
+ "payment-signature": headerGet("payment-signature"),
72
+ "x-payment": headerGet("x-payment"),
73
+ "x-request-id": headerGet("x-request-id"),
74
+ "idempotency-key": headerGet("idempotency-key"),
75
+ },
76
+ body: req.body
77
+ };
78
+
79
+ client.processPaywall(opts, request).then((result) => {
80
+ switch (result.outcome) {
81
+ case "charged":
82
+ case "crypto_paid":
83
+ req.paymentContext = result.paymentContext;
84
+ next();
85
+ return;
86
+ case "payment_required":
87
+ for (const [key, value] of Object.entries(result.headers)) {
88
+ res.set(key, value);
89
+ }
90
+ res.status(result.statusCode).json(result.body);
91
+ return;
92
+ case "error":
93
+ res.status(result.statusCode).json(result.body);
94
+ return;
95
+ }
96
+ }).catch((err) => next(err));
97
+ };
98
+ }
99
+
100
+ return { charge: client.charge, paywall };
101
+ }
@@ -0,0 +1,120 @@
1
+ import { createAgentSpendClient } from "../core/client.js";
2
+ import type {
3
+ AgentSpendOptions,
4
+ ChargeOptions,
5
+ ChargeResponse,
6
+ PaywallOptions,
7
+ PaywallPaymentContext,
8
+ PaywallRequest
9
+ } from "../core/types.js";
10
+
11
+ export * from "../core/index.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Fastify type abstractions (avoid hard dependency on fastify)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ interface FastifyRequest {
18
+ body: unknown;
19
+ url: string;
20
+ method: string;
21
+ headers: Record<string, string | string[] | undefined>;
22
+ paymentContext?: PaywallPaymentContext;
23
+ }
24
+
25
+ interface FastifyReply {
26
+ code(statusCode: number): FastifyReply;
27
+ header(name: string, value: string): FastifyReply;
28
+ send(body: unknown): FastifyReply;
29
+ }
30
+
31
+ interface FastifyInstance {
32
+ decorate(name: string, value: unknown): void;
33
+ decorateRequest(name: string, value: unknown): void;
34
+ addHook(name: string, handler: (req: FastifyRequest, reply: FastifyReply) => Promise<void>): void;
35
+ }
36
+
37
+ type DoneCallback = (err?: Error) => void;
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Payment context helper
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export function getPaymentContext(req: FastifyRequest): PaywallPaymentContext | null {
44
+ return req.paymentContext ?? null;
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Public interface
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export interface AgentSpend {
52
+ charge(cardId: string, opts: ChargeOptions): Promise<ChargeResponse>;
53
+ paywall(opts: PaywallOptions): (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
54
+ plugin(opts: PaywallOptions): (fastify: FastifyInstance, _options: unknown, done: DoneCallback) => void;
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Factory
59
+ // ---------------------------------------------------------------------------
60
+
61
+ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
62
+ const client = createAgentSpendClient(options);
63
+
64
+ function paywall(opts: PaywallOptions) {
65
+ return async function preHandler(
66
+ req: FastifyRequest,
67
+ reply: FastifyReply
68
+ ): Promise<void> {
69
+ const headerGet = (name: string): string | undefined => {
70
+ const val = req.headers[name];
71
+ return Array.isArray(val) ? val[0] : val;
72
+ };
73
+
74
+ const request: PaywallRequest = {
75
+ url: req.url,
76
+ method: req.method,
77
+ headers: {
78
+ "x-card-id": headerGet("x-card-id"),
79
+ "payment-signature": headerGet("payment-signature"),
80
+ "x-payment": headerGet("x-payment"),
81
+ "x-request-id": headerGet("x-request-id"),
82
+ "idempotency-key": headerGet("idempotency-key"),
83
+ },
84
+ body: req.body
85
+ };
86
+
87
+ const result = await client.processPaywall(opts, request);
88
+
89
+ switch (result.outcome) {
90
+ case "charged":
91
+ case "crypto_paid":
92
+ req.paymentContext = result.paymentContext;
93
+ return;
94
+ case "payment_required":
95
+ for (const [key, value] of Object.entries(result.headers)) {
96
+ reply.header(key, value);
97
+ }
98
+ reply.code(result.statusCode).send(result.body);
99
+ return;
100
+ case "error":
101
+ reply.code(result.statusCode).send(result.body);
102
+ return;
103
+ }
104
+ };
105
+ }
106
+
107
+ function plugin(opts: PaywallOptions) {
108
+ return function agentSpendPlugin(
109
+ fastify: FastifyInstance,
110
+ _options: unknown,
111
+ done: DoneCallback
112
+ ): void {
113
+ fastify.decorateRequest("paymentContext", null);
114
+ fastify.addHook("preHandler", paywall(opts));
115
+ done();
116
+ };
117
+ }
118
+
119
+ return { charge: client.charge, paywall, plugin };
120
+ }
@@ -0,0 +1,97 @@
1
+ import { createAgentSpendClient } from "../core/client.js";
2
+ import type {
3
+ AgentSpendOptions,
4
+ ChargeOptions,
5
+ ChargeResponse,
6
+ PaywallOptions,
7
+ PaywallPaymentContext,
8
+ PaywallRequest
9
+ } from "../core/types.js";
10
+
11
+ export * from "../core/index.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Hono context abstraction
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export interface HonoContextLike {
18
+ req: {
19
+ header(name: string): string | undefined;
20
+ json(): Promise<unknown>;
21
+ url: string;
22
+ method: string;
23
+ };
24
+ json(body: unknown, status?: number): Response;
25
+ header(name: string, value: string): void;
26
+ set(key: string, value: unknown): void;
27
+ get(key: string): unknown;
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Payment context helper
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const PAYMENT_CONTEXT_KEY = "payment";
35
+
36
+ export function getPaymentContext(c: HonoContextLike): PaywallPaymentContext | null {
37
+ const ctx = c.get(PAYMENT_CONTEXT_KEY);
38
+ return (ctx as PaywallPaymentContext) ?? null;
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Public interface
43
+ // ---------------------------------------------------------------------------
44
+
45
+ export interface AgentSpend {
46
+ charge(cardId: string, opts: ChargeOptions): Promise<ChargeResponse>;
47
+ paywall(opts: PaywallOptions): (c: HonoContextLike, next: () => Promise<void>) => Promise<Response | void>;
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Factory
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
55
+ const client = createAgentSpendClient(options);
56
+
57
+ function paywall(opts: PaywallOptions) {
58
+ return async function paywallMiddleware(
59
+ c: HonoContextLike,
60
+ next: () => Promise<void>
61
+ ): Promise<Response | void> {
62
+ const body: unknown = await c.req.json().catch(() => ({}));
63
+
64
+ const request: PaywallRequest = {
65
+ url: c.req.url,
66
+ method: c.req.method,
67
+ headers: {
68
+ "x-card-id": c.req.header("x-card-id"),
69
+ "payment-signature": c.req.header("payment-signature"),
70
+ "x-payment": c.req.header("x-payment"),
71
+ "x-request-id": c.req.header("x-request-id"),
72
+ "idempotency-key": c.req.header("idempotency-key"),
73
+ },
74
+ body
75
+ };
76
+
77
+ const result = await client.processPaywall(opts, request);
78
+
79
+ switch (result.outcome) {
80
+ case "charged":
81
+ case "crypto_paid":
82
+ c.set(PAYMENT_CONTEXT_KEY, result.paymentContext);
83
+ await next();
84
+ return;
85
+ case "payment_required":
86
+ for (const [key, value] of Object.entries(result.headers)) {
87
+ c.header(key, value);
88
+ }
89
+ return c.json(result.body, result.statusCode);
90
+ case "error":
91
+ return c.json(result.body, result.statusCode);
92
+ }
93
+ };
94
+ }
95
+
96
+ return { charge: client.charge, paywall };
97
+ }
@@ -0,0 +1,92 @@
1
+ import { createAgentSpendClient } from "../core/client.js";
2
+ import type {
3
+ AgentSpendOptions,
4
+ ChargeOptions,
5
+ ChargeResponse,
6
+ PaywallOptions,
7
+ PaywallPaymentContext,
8
+ PaywallRequest
9
+ } from "../core/types.js";
10
+
11
+ export * from "../core/index.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Next.js type abstractions (avoid hard dependency on next)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ interface NextRequest {
18
+ url: string;
19
+ method: string;
20
+ headers: {
21
+ get(name: string): string | null;
22
+ };
23
+ json(): Promise<unknown>;
24
+ }
25
+
26
+ interface NextResponseInit {
27
+ status?: number;
28
+ headers?: Record<string, string>;
29
+ }
30
+
31
+ declare const NextResponse: {
32
+ json(body: unknown, init?: NextResponseInit): Response;
33
+ };
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Public interface
37
+ // ---------------------------------------------------------------------------
38
+
39
+ export interface AgentSpend {
40
+ charge(cardId: string, opts: ChargeOptions): Promise<ChargeResponse>;
41
+ withPaywall(
42
+ opts: PaywallOptions,
43
+ handler: (req: NextRequest, paymentContext: PaywallPaymentContext) => Promise<Response>
44
+ ): (req: NextRequest) => Promise<Response>;
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Factory
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
52
+ const client = createAgentSpendClient(options);
53
+
54
+ function withPaywall(
55
+ opts: PaywallOptions,
56
+ handler: (req: NextRequest, paymentContext: PaywallPaymentContext) => Promise<Response>
57
+ ) {
58
+ return async function wrappedHandler(req: NextRequest): Promise<Response> {
59
+ const body: unknown = await req.json().catch(() => ({}));
60
+
61
+ const request: PaywallRequest = {
62
+ url: req.url,
63
+ method: req.method,
64
+ headers: {
65
+ "x-card-id": req.headers.get("x-card-id") ?? undefined,
66
+ "payment-signature": req.headers.get("payment-signature") ?? undefined,
67
+ "x-payment": req.headers.get("x-payment") ?? undefined,
68
+ "x-request-id": req.headers.get("x-request-id") ?? undefined,
69
+ "idempotency-key": req.headers.get("idempotency-key") ?? undefined,
70
+ },
71
+ body
72
+ };
73
+
74
+ const result = await client.processPaywall(opts, request);
75
+
76
+ switch (result.outcome) {
77
+ case "charged":
78
+ case "crypto_paid":
79
+ return handler(req, result.paymentContext);
80
+ case "payment_required":
81
+ return Response.json(result.body, {
82
+ status: result.statusCode,
83
+ headers: result.headers
84
+ });
85
+ case "error":
86
+ return Response.json(result.body, { status: result.statusCode });
87
+ }
88
+ };
89
+ }
90
+
91
+ return { charge: client.charge, withPaywall };
92
+ }