@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.
- package/dist/core/client.d.ts +6 -0
- package/dist/core/client.js +308 -0
- package/dist/core/error.d.ts +5 -0
- package/dist/core/error.js +9 -0
- package/dist/core/helpers.d.ts +8 -0
- package/dist/core/helpers.js +77 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +4 -0
- package/dist/{index.d.ts → core/types.d.ts} +21 -22
- package/dist/core/types.js +4 -0
- package/dist/express/index.d.ts +22 -0
- package/dist/express/index.js +50 -0
- package/dist/fastify/index.d.ts +27 -0
- package/dist/fastify/index.js +58 -0
- package/dist/hono/index.d.ts +20 -0
- package/dist/hono/index.js +49 -0
- package/dist/next/index.d.ts +15 -0
- package/dist/next/index.js +39 -0
- package/package.json +50 -3
- package/src/core/client.ts +433 -0
- package/src/core/error.ts +10 -0
- package/src/core/helpers.ts +87 -0
- package/src/core/index.ts +5 -0
- package/src/core/types.ts +104 -0
- package/src/express/index.ts +101 -0
- package/src/fastify/index.ts +120 -0
- package/src/hono/index.ts +97 -0
- package/src/next/index.ts +92 -0
- package/dist/index.js +0 -420
- package/src/index.ts +0 -662
|
@@ -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,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
|
+
}
|