moltspay 2.0.0 → 2.4.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.
Files changed (50) hide show
  1. package/.env.example +23 -0
  2. package/CHANGELOG.md +551 -0
  3. package/README.md +466 -8
  4. package/dist/cdp/index.js.map +1 -1
  5. package/dist/cdp/index.mjs.map +1 -1
  6. package/dist/chains/index.d.mts +69 -4
  7. package/dist/chains/index.d.ts +69 -4
  8. package/dist/chains/index.js +45 -1
  9. package/dist/chains/index.js.map +1 -1
  10. package/dist/chains/index.mjs +39 -1
  11. package/dist/chains/index.mjs.map +1 -1
  12. package/dist/cli/index.js +5444 -2126
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/index.mjs +5457 -2133
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/client/index.d.mts +307 -1
  17. package/dist/client/index.d.ts +307 -1
  18. package/dist/client/index.js +639 -34
  19. package/dist/client/index.js.map +1 -1
  20. package/dist/client/index.mjs +656 -52
  21. package/dist/client/index.mjs.map +1 -1
  22. package/dist/client/web/index.mjs.map +1 -1
  23. package/dist/facilitators/index.d.mts +512 -10
  24. package/dist/facilitators/index.d.ts +512 -10
  25. package/dist/facilitators/index.js +925 -13
  26. package/dist/facilitators/index.js.map +1 -1
  27. package/dist/facilitators/index.mjs +906 -12
  28. package/dist/facilitators/index.mjs.map +1 -1
  29. package/dist/index.d.mts +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +2843 -551
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +2849 -558
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/mcp/index.js +635 -32
  36. package/dist/mcp/index.js.map +1 -1
  37. package/dist/mcp/index.mjs +660 -57
  38. package/dist/mcp/index.mjs.map +1 -1
  39. package/dist/server/index.d.mts +252 -11
  40. package/dist/server/index.d.ts +252 -11
  41. package/dist/server/index.js +2049 -261
  42. package/dist/server/index.js.map +1 -1
  43. package/dist/server/index.mjs +2049 -261
  44. package/dist/server/index.mjs.map +1 -1
  45. package/dist/verify/index.js.map +1 -1
  46. package/dist/verify/index.mjs.map +1 -1
  47. package/dist/wallet/index.js.map +1 -1
  48. package/dist/wallet/index.mjs.map +1 -1
  49. package/package.json +14 -2
  50. package/schemas/moltspay.services.schema.json +127 -16
@@ -17,6 +17,12 @@ interface ClientConfig {
17
17
  * accepts wins. e.g. `["base", "alipay"]`.
18
18
  */
19
19
  railPreference?: string[];
20
+ /**
21
+ * Buyer identity for the custodial balance rail (2.2.0). When set and the
22
+ * server's 402 offers `balance`, the client can pay password-free by
23
+ * carrying this id in the X-Payment payload. Treat it like a bearer token.
24
+ */
25
+ buyerId?: string;
20
26
  }
21
27
  interface WalletData {
22
28
  address: string;
@@ -75,6 +81,128 @@ interface MoltsPayClientOptions {
75
81
  alipaySessionId?: string;
76
82
  }
77
83
 
84
+ interface X402PaymentRequirements {
85
+ scheme: string;
86
+ network: string;
87
+ amount?: string;
88
+ asset?: string;
89
+ payTo?: string;
90
+ maxTimeoutSeconds?: number;
91
+ extra?: Record<string, unknown>;
92
+ maxAmountRequired?: string;
93
+ resource?: string;
94
+ description?: string;
95
+ }
96
+
97
+ /**
98
+ * WechatClient — buyer-side completion for the WeChat Pay Native rail (2.1.0).
99
+ *
100
+ * Mirrors {@link AlipayClient} so paying via WeChat looks the same as awaiting
101
+ * an EVM settle. The flow is simpler than Alipay's: the SERVER already placed
102
+ * the Native order when it built the 402, so its `wechatpay-native` accepts[]
103
+ * entry carries `extra.code_url` + `extra.out_trade_no`. There is no buyer-side
104
+ * CLI/wallet and no order creation here — a human scans the code_url with the
105
+ * WeChat app, and the client polls the resource endpoint, re-submitting the
106
+ * `out_trade_no` as the x402 payment proof until the server verifies the order
107
+ * as paid (`trade_state === SUCCESS`) and delivers the resource.
108
+ *
109
+ * 1. surface code_url + out_trade_no to the caller (onPaymentPending → QR)
110
+ * 2. poll POST <resource> with X-Payment{out_trade_no} every pollIntervalMs
111
+ * - 200 → paid, return the resource body
112
+ * - 402 → not yet paid, keep polling
113
+ * - other → terminal error
114
+ * 3. give up after timeoutMs
115
+ */
116
+
117
+ interface WechatPendingInfo {
118
+ /** `weixin://wxpay/bizpayurl?pr=...` — render as a QR for the payer to scan. */
119
+ codeUrl: string;
120
+ /** Merchant order id; the proof echoed back to the server on each poll. */
121
+ outTradeNo: string;
122
+ }
123
+ type WechatSessionStatus = 'pending' | 'paid' | 'completed' | 'expired' | 'cancelled' | 'failed';
124
+ interface WechatPaymentSession {
125
+ paymentSessionId: string;
126
+ status: WechatSessionStatus;
127
+ resourceUrl: string;
128
+ method: 'GET' | 'POST';
129
+ data?: string;
130
+ requirement: X402PaymentRequirements;
131
+ codeUrl: string;
132
+ outTradeNo: string;
133
+ createdAt: string;
134
+ updatedAt: string;
135
+ expiresAt: string;
136
+ context?: Record<string, unknown>;
137
+ lastHttpStatus?: number;
138
+ lastError?: string;
139
+ resultBody?: string;
140
+ }
141
+ interface WechatPayOptions {
142
+ /** Resource/execute URL to re-POST the proof to (same one that issued the 402). */
143
+ resourceUrl: string;
144
+ /** The server's `wechatpay-native` accepts[] entry (carries extra.code_url/out_trade_no). */
145
+ requirement: X402PaymentRequirements;
146
+ /** HTTP method of the original resource request (default POST). */
147
+ method?: 'GET' | 'POST';
148
+ /** Raw JSON body to replay on each poll (the `{ service, params }` payload). */
149
+ data?: string;
150
+ /** Surface the QR code_url + out_trade_no to the UI/caller (called once). */
151
+ onPaymentPending?: (info: WechatPendingInfo) => void;
152
+ /** Poll cadence in ms (default 3000). */
153
+ pollIntervalMs?: number;
154
+ /** Overall budget in ms (default 300000). */
155
+ timeoutMs?: number;
156
+ /** Cancellation. */
157
+ signal?: AbortSignal;
158
+ }
159
+ interface WechatStartOptions extends WechatPayOptions {
160
+ /** Optional channel/service metadata persisted with the session for recovery. */
161
+ context?: Record<string, unknown>;
162
+ /** Stable session id, mostly for tests/integration. Defaults to `mpay_sess_${uuid}`. */
163
+ paymentSessionId?: string;
164
+ }
165
+ interface WechatPaymentResult {
166
+ /** Raw resource body returned by the server on success (HTTP 200). */
167
+ body: string;
168
+ status: number;
169
+ }
170
+ interface WechatClientOptions {
171
+ /** Config directory; sessions are stored under `<configDir>/wechat-sessions`. */
172
+ configDir?: string;
173
+ /** Injectable clock for tests. */
174
+ now?: () => number;
175
+ }
176
+ declare class WechatClient {
177
+ private readonly configDir;
178
+ private readonly sessionDir;
179
+ private readonly now;
180
+ constructor(options?: WechatClientOptions);
181
+ pay402(opts: WechatPayOptions): Promise<WechatPaymentResult>;
182
+ /**
183
+ * Start a recoverable WeChat payment session. This returns immediately after
184
+ * persisting `out_trade_no`, QR payload, original request body, and context.
185
+ */
186
+ start402(opts: WechatStartOptions): WechatPaymentSession;
187
+ /**
188
+ * Query a persisted session once by replaying the original request with the
189
+ * stored `out_trade_no` proof. 200 means paid + fulfilled; 402 means pending.
190
+ */
191
+ status(identifier: string): Promise<WechatPaymentSession>;
192
+ /** Poll a session until it completes, expires, fails, or is aborted. */
193
+ pollSession(identifier: string, opts?: Pick<WechatPayOptions, 'pollIntervalMs' | 'timeoutMs' | 'signal'>): Promise<WechatPaymentSession>;
194
+ /** `fulfill` is an idempotent status check: paid sessions return stored body. */
195
+ fulfill(identifier: string): Promise<WechatPaymentSession>;
196
+ /** Mark a local session cancelled. Closing the merchant order is server-side. */
197
+ cancel(identifier: string): WechatPaymentSession;
198
+ listSessions(): WechatPaymentSession[];
199
+ private buildPaymentHeader;
200
+ private loadSession;
201
+ private saveSession;
202
+ private updateSession;
203
+ private ensureSessionDir;
204
+ }
205
+
78
206
  /**
79
207
  * MoltsPay Client - Pay for AI Agent services
80
208
  *
@@ -86,6 +214,20 @@ interface MoltsPayClientOptions {
86
214
  * const result = await client.pay('http://provider:3000', 'text-to-video', { prompt: '...' });
87
215
  */
88
216
 
217
+ /** A recoverable balance top-up session, persisted under `<configDir>/balance-topup-sessions`. */
218
+ interface BalanceTopupSession {
219
+ out_trade_no: string;
220
+ buyer_id: string;
221
+ pack: string;
222
+ server_url: string;
223
+ code_url: string;
224
+ status: 'pending' | 'credited' | 'expired';
225
+ created_at: string;
226
+ expires_at: string;
227
+ context?: Record<string, any>;
228
+ tx_id?: string;
229
+ balance?: string;
230
+ }
89
231
  interface PayOptions {
90
232
  /** Token to pay with (default: USDC, or auto-select based on balance) */
91
233
  token?: TokenSymbol;
@@ -101,6 +243,11 @@ interface PayOptions {
101
243
  * alipay-bot-backed {@link AlipayClient} and needs no EVM wallet.
102
244
  */
103
245
  rail?: string;
246
+ /**
247
+ * Balance rail (2.2.0): buyer identity for password-free payment.
248
+ * Falls back to the persisted `config.buyerId`. Bearer semantics.
249
+ */
250
+ buyerId?: string;
104
251
  /** Alipay: surfaced once the payment URL + tradeNo are known. */
105
252
  onPaymentPending?: (info: {
106
253
  paymentUrl: string;
@@ -113,6 +260,39 @@ interface PayOptions {
113
260
  timeoutMs?: number;
114
261
  /** Cancellation (alipay poll loop). */
115
262
  signal?: AbortSignal;
263
+ /**
264
+ * WeChat: after `startWechatPayment()`, let the SDK client poll in the
265
+ * background and invoke the callbacks below. `pay --rail wechat` always polls
266
+ * because it is the blocking terminal wrapper.
267
+ */
268
+ autoPoll?: boolean;
269
+ /** WeChat: called when the background poll fulfills the resource. */
270
+ onWechatPaymentCompleted?: (session: WechatPaymentSession) => void | Promise<void>;
271
+ /** WeChat: called when background poll expires, fails, or is cancelled. */
272
+ onWechatPaymentFailed?: (session: WechatPaymentSession) => void | Promise<void>;
273
+ /**
274
+ * Balance rail (2.3.0): when a password-free deduct finds an insufficient
275
+ * balance, auto-fund via a WeChat top-up pack, then retry once. Default true.
276
+ * Set false to fail fast instead.
277
+ */
278
+ autoTopup?: boolean;
279
+ /**
280
+ * Balance rail top-up mode (2.5.0). `'auto'` (default): block through the
281
+ * top-up scan + retry (terminal use). `'manual'`: on an insufficient
282
+ * balance, create the order, surface the QR via `onTopupRequired`, and
283
+ * return a `{ status: 'topup_required', out_trade_no, code_url, pack,
284
+ * server_url }` result WITHOUT polling — the caller confirms + retries in
285
+ * later turns (recoverable flow for turn-based agents).
286
+ */
287
+ topupMode?: 'auto' | 'manual';
288
+ /** Balance rail: pack to fund with; defaults to the server's `default_pack`. */
289
+ topupPack?: string;
290
+ /** Balance rail: poll interval while waiting for the top-up scan (default 2000ms). */
291
+ topupPollIntervalMs?: number;
292
+ /** Balance rail: called when a top-up pack QR must be shown (scan once). */
293
+ onTopupRequired?: (pack: string, codeUrl: string) => void;
294
+ /** Balance rail: called after the top-up is credited (new balance). */
295
+ onTopupCredited?: (balance: string) => void;
116
296
  }
117
297
  declare class MoltsPayClient {
118
298
  private configDir;
@@ -171,6 +351,132 @@ declare class MoltsPayClient {
171
351
  * resource body.
172
352
  */
173
353
  private payViaAlipay;
354
+ /**
355
+ * Pay for a service over the WeChat Pay Native fiat rail (2.1.0).
356
+ *
357
+ * Mirrors {@link payViaAlipay} and needs no EVM wallet. The server placed the
358
+ * Native order when it built the 402, so its `wechatpay-native` accepts[]
359
+ * entry already carries `extra.code_url` + `extra.out_trade_no`. Flow: hit the
360
+ * resource to get the 402, confirm the server offers the wechat rail, surface
361
+ * the code_url (caller renders a QR), then poll the resource with the
362
+ * out_trade_no proof until the server verifies the order paid and delivers.
363
+ */
364
+ private payViaWechat;
365
+ /**
366
+ * The ethers wallet used to sign balance-rail deductions: the client's EVM
367
+ * wallet if it has one, else a per-configDir identity key persisted at
368
+ * `<configDir>/balance-identity.key` (0600) so a balance-only client works
369
+ * without a full crypto wallet. Under `agent 统一代付`, one key spends every
370
+ * account the agent tops up.
371
+ */
372
+ private balanceSigner;
373
+ /** The balance-rail spending signer address (lowercase 0x…). Stable per
374
+ * configDir; this is the identity the server TOFU-binds and later verifies. */
375
+ getBalanceSignerAddress(): string;
376
+ /** The buyer id for the balance rail: explicit option > persisted config. */
377
+ private resolveBuyerId;
378
+ /**
379
+ * Pay via the custodial balance rail (2.2.0, password-free).
380
+ *
381
+ * No wallet, no QR: the request carries `{buyer_id, request_id}` in the
382
+ * X-Payment payload and the server deducts the prepaid balance atomically
383
+ * before running the skill. The client-generated `request_id` makes the
384
+ * charge idempotent — a network retry can never double-deduct.
385
+ */
386
+ private payViaBalance;
387
+ /** One password-free deduct attempt. Never throws on an HTTP error; the
388
+ * caller inspects `{ ok, status, error }` to decide whether to auto-fund. */
389
+ private balanceDeduct;
390
+ /**
391
+ * Non-blocking: POST /balance/topup/order, persist a recoverable session, and
392
+ * return at once (no polling). Use with {@link confirmBalanceTopup} for
393
+ * turn-based agents; the blocking {@link topupBalancePack} is built on this.
394
+ */
395
+ createBalanceTopupOrder(serverUrl: string, opts?: {
396
+ pack?: string;
397
+ buyerId?: string;
398
+ context?: Record<string, any>;
399
+ }): Promise<{
400
+ outTradeNo: string;
401
+ codeUrl: string;
402
+ pack: string;
403
+ maxTimeoutSeconds: number;
404
+ }>;
405
+ /**
406
+ * One-shot: POST /balance/topup/confirm for a single order. No polling.
407
+ * Updates the persisted session on credit. `serverUrl` defaults to the one
408
+ * recorded in the session (recover by out_trade_no alone).
409
+ */
410
+ confirmBalanceTopup(outTradeNo: string, opts?: {
411
+ serverUrl?: string;
412
+ }): Promise<{
413
+ credited: boolean;
414
+ pending?: boolean;
415
+ balance?: string;
416
+ txId?: string;
417
+ reason?: string;
418
+ }>;
419
+ /**
420
+ * Blocking terminal wrapper: create the order then poll confirm until the
421
+ * scan is credited (or the order expires). Built on
422
+ * {@link createBalanceTopupOrder} + {@link confirmBalanceTopup}.
423
+ */
424
+ topupBalancePack(serverUrl: string, opts?: {
425
+ pack?: string;
426
+ buyerId?: string;
427
+ pollIntervalMs?: number;
428
+ signal?: AbortSignal;
429
+ onCodeUrl?: (pack: string, codeUrl: string) => void;
430
+ }): Promise<{
431
+ balance: string;
432
+ outTradeNo: string;
433
+ txId?: string;
434
+ }>;
435
+ private balanceTopupSessionDir;
436
+ private saveBalanceTopupSession;
437
+ /** Read a persisted top-up session by out_trade_no, or null. */
438
+ getBalanceTopupSession(outTradeNo: string): BalanceTopupSession | null;
439
+ /** List persisted top-up sessions, newest first. */
440
+ listBalanceTopupSessions(): BalanceTopupSession[];
441
+ /** Abortable sleep used by the top-up poll loop. */
442
+ private sleep;
443
+ /** Persist the buyer id used by the balance rail (bearer semantics). */
444
+ setBuyerId(buyerId: string): void;
445
+ /** GET /balance — custodial balance, limits, and today's spend for a buyer. */
446
+ getBuyerBalance(serverUrl: string, buyerId?: string): Promise<Record<string, any>>;
447
+ /**
448
+ * POST /balance/topup — report an externally settled payment (on-chain
449
+ * tx hash / Alipay trade_no / WeChat out_trade_no) so the server verifies
450
+ * and credits the ledger. Idempotent per reference.
451
+ */
452
+ topupBalance(serverUrl: string, opts: {
453
+ rail: 'crypto' | 'alipay' | 'wechat';
454
+ amount: string;
455
+ buyerId?: string;
456
+ txHash?: string;
457
+ chain?: string;
458
+ tradeNo?: string;
459
+ outTradeNo?: string;
460
+ }): Promise<Record<string, any>>;
461
+ /** GET /balance/transactions — ledger history for a buyer, newest first. */
462
+ listBalanceTransactions(serverUrl: string, opts?: {
463
+ buyerId?: string;
464
+ limit?: number;
465
+ offset?: number;
466
+ }): Promise<Record<string, any>>;
467
+ /**
468
+ * Start a recoverable WeChat Pay Native session and return immediately with
469
+ * QR metadata. The SDK client persists enough context to poll/fulfill later.
470
+ */
471
+ startWechatPayment(serverUrl: string, service: string, params: Record<string, any>, options?: PayOptions): Promise<WechatPaymentSession>;
472
+ /** Query a persisted WeChat session once. */
473
+ getWechatPaymentStatus(identifier: string): Promise<WechatPaymentSession>;
474
+ /** Idempotently fulfill a paid WeChat session, returning stored or fetched result. */
475
+ fulfillWechatPayment(identifier: string): Promise<WechatPaymentSession>;
476
+ /** Mark a local WeChat session as cancelled. */
477
+ cancelWechatPayment(identifier: string): WechatPaymentSession;
478
+ /** List persisted WeChat sessions, newest first. */
479
+ listWechatPaymentSessions(): WechatPaymentSession[];
174
480
  /**
175
481
  * Handle MPP (Machine Payments Protocol) payment flow
176
482
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -280,4 +586,4 @@ declare class MoltsPayClient {
280
586
  }): Promise<any>;
281
587
  }
282
588
 
283
- export { type ClientConfig, MoltsPayClient, type MoltsPayClientOptions, type PayOptions, type PaymentRequired, type ProviderInfo, type ServiceInfo, type ServicesResponse, type VerifyResponse, type WalletData };
589
+ export { type BalanceTopupSession, type ClientConfig, MoltsPayClient, type MoltsPayClientOptions, type PayOptions, type PaymentRequired, type ProviderInfo, type ServiceInfo, type ServicesResponse, type VerifyResponse, type WalletData, WechatClient, type WechatClientOptions, type WechatPayOptions, type WechatPaymentResult, type WechatPaymentSession, type WechatPendingInfo, type WechatSessionStatus, type WechatStartOptions };