@moneymq/sdk 0.3.2 → 0.7.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.
package/dist/index.js CHANGED
@@ -20,13 +20,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ EventStream: () => EventStream,
23
24
  MoneyMQ: () => MoneyMQ,
25
+ PaymentRequiredError: () => PaymentRequiredError,
26
+ buildEventStreamUrl: () => buildEventStreamUrl,
27
+ createEventStream: () => createEventStream,
24
28
  fetchConfig: () => fetchConfig,
25
- getRpcUrl: () => getRpcUrl
29
+ getRpcUrl: () => getRpcUrl,
30
+ isPaymentSettlementFailed: () => isPaymentSettlementFailed,
31
+ isPaymentSettlementSucceeded: () => isPaymentSettlementSucceeded,
32
+ isPaymentVerificationFailed: () => isPaymentVerificationFailed,
33
+ isPaymentVerificationSucceeded: () => isPaymentVerificationSucceeded,
34
+ parseCloudEvent: () => parseCloudEvent
26
35
  });
27
36
  module.exports = __toCommonJS(index_exports);
28
37
 
29
38
  // src/catalog.ts
39
+ var PaymentRequiredError = class extends Error {
40
+ constructor(message, paymentRequirements, raw) {
41
+ super(message);
42
+ this.paymentRequirements = paymentRequirements;
43
+ this.raw = raw;
44
+ this.name = "PaymentRequiredError";
45
+ }
46
+ };
30
47
  var ProductsAPI = class {
31
48
  constructor(config) {
32
49
  this.config = config;
@@ -67,7 +84,15 @@ var ProductsAPI = class {
67
84
  if (params?.limit) query.set("limit", String(params.limit));
68
85
  if (params?.startingAfter) query.set("starting_after", params.startingAfter);
69
86
  const queryString = query.toString();
70
- return this.request("GET", `/catalog/v1/products${queryString ? `?${queryString}` : ""}`);
87
+ const result = await this.request(
88
+ "GET",
89
+ `/catalog/v1/products${queryString ? `?${queryString}` : ""}`
90
+ );
91
+ result.data = result.data.map((product) => ({
92
+ ...product,
93
+ accessUrl: `${this.config.endpoint}/catalog/v1/products/${product.id}/access`
94
+ }));
95
+ return result;
71
96
  }
72
97
  /**
73
98
  * Update a product
@@ -81,6 +106,59 @@ var ProductsAPI = class {
81
106
  async delete(id) {
82
107
  return this.request("DELETE", `/catalog/v1/products/${id}`);
83
108
  }
109
+ /**
110
+ * Access a product - gated by x402 payment
111
+ *
112
+ * This endpoint requires payment. If no payment header is provided (or payment is invalid),
113
+ * throws a PaymentRequiredError with the payment requirements.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * try {
118
+ * // First attempt without payment - will throw PaymentRequiredError
119
+ * const access = await moneymq.catalog.products.access('surfnet-max');
120
+ * } catch (error) {
121
+ * if (error instanceof PaymentRequiredError) {
122
+ * // Get payment requirements and create payment
123
+ * const requirements = error.paymentRequirements[0];
124
+ * const paymentHeader = await createPayment(requirements);
125
+ *
126
+ * // Retry with payment
127
+ * const access = await moneymq.catalog.products.access('surfnet-max', {
128
+ * paymentHeader,
129
+ * });
130
+ * }
131
+ * }
132
+ * ```
133
+ */
134
+ async access(id, params) {
135
+ const url = `${this.config.endpoint}/catalog/v1/products/${id}/access`;
136
+ const headers = { "Content-Type": "application/json" };
137
+ if (this.config.secret) {
138
+ headers["Authorization"] = `Bearer ${this.config.secret}`;
139
+ }
140
+ if (params?.paymentHeader) {
141
+ headers["X-Payment"] = params.paymentHeader;
142
+ }
143
+ const response = await fetch(url, {
144
+ method: "GET",
145
+ headers
146
+ });
147
+ if (response.status === 402) {
148
+ const x402Response = await response.json().catch(() => ({}));
149
+ const paymentRequirements = x402Response.accepts || [];
150
+ throw new PaymentRequiredError(
151
+ "Payment required",
152
+ paymentRequirements,
153
+ x402Response
154
+ );
155
+ }
156
+ if (!response.ok) {
157
+ const errorData = await response.json().catch(() => ({}));
158
+ throw new Error(errorData.message || `Request failed: ${response.status}`);
159
+ }
160
+ return response.json();
161
+ }
84
162
  };
85
163
  var PricesAPI = class {
86
164
  constructor(config) {
@@ -284,15 +362,92 @@ var WebhooksAPI = class {
284
362
  return this.request("POST", "/payment/v1/webhooks/test", { event, data });
285
363
  }
286
364
  };
365
+ var PaymentIntentsAPI = class {
366
+ constructor(config) {
367
+ this.request = createRequester(config);
368
+ }
369
+ /**
370
+ * Create a payment intent
371
+ * Use this for simple payments without the full checkout session flow
372
+ */
373
+ async create(params) {
374
+ return this.request("POST", "/catalog/v1/payment_intents", params);
375
+ }
376
+ /**
377
+ * Retrieve a payment intent
378
+ */
379
+ async retrieve(id) {
380
+ return this.request("GET", `/catalog/v1/payment_intents/${id}`);
381
+ }
382
+ /**
383
+ * Confirm a payment intent
384
+ * This triggers the actual payment (and x402 flow if required)
385
+ */
386
+ async confirm(id) {
387
+ return this.request("POST", `/catalog/v1/payment_intents/${id}/confirm`, {});
388
+ }
389
+ /**
390
+ * Cancel a payment intent
391
+ */
392
+ async cancel(id) {
393
+ return this.request("POST", `/catalog/v1/payment_intents/${id}/cancel`, {});
394
+ }
395
+ };
287
396
  var PaymentAPI = class {
288
397
  constructor(config) {
289
398
  this.request = createRequester(config);
290
399
  this.checkout = new CheckoutAPI(config);
400
+ this.intents = new PaymentIntentsAPI(config);
291
401
  this.links = new LinksAPI(config);
292
402
  this.customers = new CustomersAPI(config);
293
403
  this.payouts = new PayoutsAPI(config);
294
404
  this.webhooks = new WebhooksAPI(config);
295
405
  }
406
+ /**
407
+ * Simple one-liner payment - creates a checkout session with inline product data
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * const result = await moneymq.payment.pay({
412
+ * amount: 999,
413
+ * currency: 'usd',
414
+ * productName: 'Pro Plan',
415
+ * productId: 'pro-plan',
416
+ * customer: 'wallet_address',
417
+ * });
418
+ * ```
419
+ */
420
+ async pay(params) {
421
+ const session = await this.request("POST", "/catalog/v1/checkout/sessions", {
422
+ line_items: [
423
+ {
424
+ price_data: {
425
+ currency: params.currency,
426
+ unit_amount: params.amount,
427
+ product_data: {
428
+ name: params.productName,
429
+ description: params.description,
430
+ metadata: {
431
+ product_id: params.productId || params.productName.toLowerCase().replace(/\s+/g, "-")
432
+ }
433
+ }
434
+ },
435
+ quantity: 1
436
+ }
437
+ ],
438
+ customer: params.customer,
439
+ metadata: params.metadata,
440
+ mode: "payment"
441
+ });
442
+ return {
443
+ sessionId: session.id,
444
+ paymentIntentId: session.payment_intent,
445
+ clientSecret: session.client_secret,
446
+ amount: session.amount_total,
447
+ currency: session.currency,
448
+ status: "requires_confirmation"
449
+ };
450
+ }
296
451
  /**
297
452
  * Retrieve a payment by ID
298
453
  */
@@ -313,8 +468,329 @@ var PaymentAPI = class {
313
468
  }
314
469
  };
315
470
 
471
+ // src/config.ts
472
+ async function fetchConfig(apiUrl) {
473
+ const response = await fetch(`${apiUrl}/config`);
474
+ if (!response.ok) {
475
+ throw new Error(`Failed to fetch config: ${response.status}`);
476
+ }
477
+ return response.json();
478
+ }
479
+ async function getRpcUrl(apiUrl, fallback = "https://api.devnet.solana.com") {
480
+ try {
481
+ const config = await fetchConfig(apiUrl);
482
+ return config.x402.validator.rpcUrl || fallback;
483
+ } catch {
484
+ return fallback;
485
+ }
486
+ }
487
+
488
+ // src/x402.ts
489
+ var import_x402_fetch = require("x402-fetch");
490
+ var X402API = class {
491
+ constructor(config) {
492
+ this.config = config;
493
+ this.serverConfig = null;
494
+ this.sandboxAccounts = null;
495
+ }
496
+ /**
497
+ * Fetch sandbox accounts from the server
498
+ */
499
+ async fetchSandboxAccounts() {
500
+ if (this.sandboxAccounts) {
501
+ return this.sandboxAccounts;
502
+ }
503
+ const response = await fetch(`${this.config.endpoint}/sandbox/accounts`);
504
+ if (!response.ok) {
505
+ throw new Error(`Failed to fetch sandbox accounts: ${response.status}`);
506
+ }
507
+ this.sandboxAccounts = await response.json();
508
+ return this.sandboxAccounts;
509
+ }
510
+ /**
511
+ * Get a signer for a sandbox account by tag/label
512
+ *
513
+ * @param params - Parameters containing the wallet tag/label
514
+ * @returns A Signer that can be used directly with wrapFetchWithPayment
515
+ *
516
+ * @example
517
+ * ```typescript
518
+ * const payer = await moneymq.x402.getSigner({ tag: 'alice' });
519
+ * ```
520
+ */
521
+ async getSigner(params) {
522
+ const accounts = await this.fetchSandboxAccounts();
523
+ for (const networkData of Object.values(accounts)) {
524
+ for (const account of networkData.userAccounts) {
525
+ if (account.label === params.tag) {
526
+ if (!account.secretKeyHex) {
527
+ throw new Error(`Account '${params.tag}' does not have a secret key (not locally managed)`);
528
+ }
529
+ return (0, import_x402_fetch.createSigner)("solana", account.secretKeyHex);
530
+ }
531
+ }
532
+ }
533
+ throw new Error(`No sandbox account found with label '${params.tag}'`);
534
+ }
535
+ /**
536
+ * Get x402 configuration for use with wrapFetchWithPayment
537
+ *
538
+ * @returns Configuration object compatible with x402-fetch
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * const config = await moneymq.x402.getConfig();
543
+ * const fetchWithPayment = wrapFetchWithPayment(fetch, payer, undefined, undefined, config);
544
+ * ```
545
+ */
546
+ async getConfig() {
547
+ if (!this.serverConfig) {
548
+ this.serverConfig = await fetchConfig(this.config.endpoint);
549
+ }
550
+ return {
551
+ svmConfig: {
552
+ rpcUrl: this.serverConfig.x402.validator.rpcUrl
553
+ }
554
+ };
555
+ }
556
+ /**
557
+ * Get the full server configuration
558
+ *
559
+ * @returns The complete server configuration including x402 settings
560
+ */
561
+ async getServerConfig() {
562
+ if (!this.serverConfig) {
563
+ this.serverConfig = await fetchConfig(this.config.endpoint);
564
+ }
565
+ return this.serverConfig;
566
+ }
567
+ };
568
+
569
+ // src/events.ts
570
+ var EventStream = class {
571
+ constructor(endpoint, options = {}) {
572
+ this.eventSource = null;
573
+ this.state = "disconnected";
574
+ this.reconnectAttempts = 0;
575
+ this.lastEventId = null;
576
+ this.paymentHandlers = /* @__PURE__ */ new Set();
577
+ this.errorHandlers = /* @__PURE__ */ new Set();
578
+ this.stateHandlers = /* @__PURE__ */ new Set();
579
+ this.endpoint = endpoint;
580
+ console.log("[MoneyMQ SDK] EventStream constructor called with options:", JSON.stringify(options));
581
+ this.options = {
582
+ last: options.last ?? 0,
583
+ cursor: options.cursor ?? "",
584
+ streamId: options.streamId ?? "",
585
+ autoReconnect: options.autoReconnect ?? true,
586
+ reconnectDelay: options.reconnectDelay ?? 1e3,
587
+ maxReconnectAttempts: options.maxReconnectAttempts ?? 0
588
+ };
589
+ console.log("[MoneyMQ SDK] Resolved options:", JSON.stringify(this.options));
590
+ if (this.options.cursor && !this.options.streamId) {
591
+ this.lastEventId = this.options.cursor;
592
+ }
593
+ }
594
+ /**
595
+ * Whether this is a stateful stream (server tracks cursor)
596
+ */
597
+ get isStateful() {
598
+ return this.options.streamId !== "";
599
+ }
600
+ /**
601
+ * The stream ID for stateful streams
602
+ */
603
+ get streamId() {
604
+ return this.options.streamId || null;
605
+ }
606
+ /**
607
+ * Current connection state
608
+ */
609
+ get connectionState() {
610
+ return this.state;
611
+ }
612
+ /**
613
+ * Current cursor (last received event ID)
614
+ */
615
+ get cursor() {
616
+ return this.lastEventId;
617
+ }
618
+ /**
619
+ * Whether the stream is currently connected
620
+ */
621
+ get isConnected() {
622
+ return this.state === "connected";
623
+ }
624
+ on(event, handler) {
625
+ switch (event) {
626
+ case "payment":
627
+ this.paymentHandlers.add(handler);
628
+ return () => this.paymentHandlers.delete(handler);
629
+ case "error":
630
+ this.errorHandlers.add(handler);
631
+ return () => this.errorHandlers.delete(handler);
632
+ case "stateChange":
633
+ this.stateHandlers.add(handler);
634
+ return () => this.stateHandlers.delete(handler);
635
+ }
636
+ }
637
+ off(event, handler) {
638
+ switch (event) {
639
+ case "payment":
640
+ this.paymentHandlers.delete(handler);
641
+ break;
642
+ case "error":
643
+ this.errorHandlers.delete(handler);
644
+ break;
645
+ case "stateChange":
646
+ this.stateHandlers.delete(handler);
647
+ break;
648
+ }
649
+ }
650
+ /**
651
+ * Connect to the event stream
652
+ */
653
+ connect() {
654
+ if (this.eventSource) {
655
+ this.eventSource.close();
656
+ }
657
+ this.setState("connecting");
658
+ const url = this.buildUrl();
659
+ console.log("[MoneyMQ SDK] Connecting to SSE:", url, "| streamId:", this.options.streamId || "(none)");
660
+ this.eventSource = new EventSource(url);
661
+ this.eventSource.onopen = () => {
662
+ this.setState("connected");
663
+ this.reconnectAttempts = 0;
664
+ };
665
+ this.eventSource.addEventListener("payment", (event) => {
666
+ try {
667
+ const cloudEvent = JSON.parse(event.data);
668
+ this.lastEventId = cloudEvent.id;
669
+ this.emitPayment(cloudEvent);
670
+ } catch (e) {
671
+ this.emitError(new Error(`Failed to parse event: ${e}`));
672
+ }
673
+ });
674
+ this.eventSource.onerror = () => {
675
+ this.eventSource?.close();
676
+ this.eventSource = null;
677
+ if (this.options.autoReconnect && this.shouldReconnect()) {
678
+ this.scheduleReconnect();
679
+ } else {
680
+ this.setState("disconnected");
681
+ this.emitError(new Error("Connection lost"));
682
+ }
683
+ };
684
+ }
685
+ /**
686
+ * Disconnect from the event stream
687
+ */
688
+ disconnect() {
689
+ if (this.eventSource) {
690
+ this.eventSource.close();
691
+ this.eventSource = null;
692
+ }
693
+ this.setState("disconnected");
694
+ this.reconnectAttempts = 0;
695
+ }
696
+ /**
697
+ * Reconnect with a new cursor
698
+ * Useful for resuming from a stored position
699
+ */
700
+ reconnectFrom(cursor) {
701
+ this.lastEventId = cursor;
702
+ this.disconnect();
703
+ this.connect();
704
+ }
705
+ buildUrl() {
706
+ const params = new URLSearchParams();
707
+ if (this.options.streamId) {
708
+ params.set("stream_id", this.options.streamId);
709
+ if (this.options.last > 0) {
710
+ params.set("last", this.options.last.toString());
711
+ }
712
+ } else {
713
+ if (this.lastEventId) {
714
+ params.set("cursor", this.lastEventId);
715
+ } else if (this.options.last > 0) {
716
+ params.set("last", this.options.last.toString());
717
+ }
718
+ }
719
+ const queryString = params.toString();
720
+ return queryString ? `${this.endpoint}/events?${queryString}` : `${this.endpoint}/events`;
721
+ }
722
+ setState(state) {
723
+ if (this.state !== state) {
724
+ this.state = state;
725
+ this.stateHandlers.forEach((handler) => handler(state));
726
+ }
727
+ }
728
+ shouldReconnect() {
729
+ if (this.options.maxReconnectAttempts === 0) return true;
730
+ return this.reconnectAttempts < this.options.maxReconnectAttempts;
731
+ }
732
+ scheduleReconnect() {
733
+ this.setState("reconnecting");
734
+ this.reconnectAttempts++;
735
+ setTimeout(() => {
736
+ if (this.state === "reconnecting") {
737
+ this.connect();
738
+ }
739
+ }, this.options.reconnectDelay);
740
+ }
741
+ emitPayment(event) {
742
+ this.paymentHandlers.forEach((handler) => handler(event));
743
+ }
744
+ emitError(error) {
745
+ this.errorHandlers.forEach((handler) => handler(error));
746
+ }
747
+ };
748
+ function createEventStream(endpoint, options) {
749
+ return new EventStream(endpoint, options);
750
+ }
751
+ function isPaymentVerificationSucceeded(event) {
752
+ return event.type === "mq.money.payment.verification.succeeded";
753
+ }
754
+ function isPaymentVerificationFailed(event) {
755
+ return event.type === "mq.money.payment.verification.failed";
756
+ }
757
+ function isPaymentSettlementSucceeded(event) {
758
+ return event.type === "mq.money.payment.settlement.succeeded";
759
+ }
760
+ function isPaymentSettlementFailed(event) {
761
+ return event.type === "mq.money.payment.settlement.failed";
762
+ }
763
+ function parseCloudEvent(data) {
764
+ try {
765
+ return JSON.parse(data);
766
+ } catch {
767
+ return null;
768
+ }
769
+ }
770
+ function buildEventStreamUrl(endpoint, options) {
771
+ const params = new URLSearchParams();
772
+ if (options?.streamId) {
773
+ params.set("stream_id", options.streamId);
774
+ if (options.last && options.last > 0) {
775
+ params.set("last", options.last.toString());
776
+ }
777
+ } else {
778
+ if (options?.cursor) {
779
+ params.set("cursor", options.cursor);
780
+ } else if (options?.last && options.last > 0) {
781
+ params.set("last", options.last.toString());
782
+ }
783
+ }
784
+ const queryString = params.toString();
785
+ return queryString ? `${endpoint}/events?${queryString}` : `${endpoint}/events`;
786
+ }
787
+
316
788
  // src/client.ts
317
789
  var MoneyMQ = class {
790
+ /** MoneyMQ API endpoint */
791
+ get endpoint() {
792
+ return this.config.endpoint;
793
+ }
318
794
  constructor(config) {
319
795
  this.config = {
320
796
  timeout: 3e4,
@@ -322,6 +798,10 @@ var MoneyMQ = class {
322
798
  };
323
799
  this.catalog = new CatalogAPI(this.config);
324
800
  this.payment = new PaymentAPI(this.config);
801
+ this.x402 = new X402API(this.config);
802
+ this.events = {
803
+ stream: (options) => new EventStream(this.config.endpoint, options)
804
+ };
325
805
  }
326
806
  /**
327
807
  * Make an authenticated request to the MoneyMQ API
@@ -359,27 +839,19 @@ var MoneyMQError = class extends Error {
359
839
  this.name = "MoneyMQError";
360
840
  }
361
841
  };
362
-
363
- // src/config.ts
364
- async function fetchConfig(apiUrl) {
365
- const response = await fetch(`${apiUrl}/config`);
366
- if (!response.ok) {
367
- throw new Error(`Failed to fetch config: ${response.status}`);
368
- }
369
- return response.json();
370
- }
371
- async function getRpcUrl(apiUrl, fallback = "https://api.devnet.solana.com") {
372
- try {
373
- const config = await fetchConfig(apiUrl);
374
- return config.x402.validator.rpcUrl || fallback;
375
- } catch {
376
- return fallback;
377
- }
378
- }
379
842
  // Annotate the CommonJS export names for ESM import in node:
380
843
  0 && (module.exports = {
844
+ EventStream,
381
845
  MoneyMQ,
846
+ PaymentRequiredError,
847
+ buildEventStreamUrl,
848
+ createEventStream,
382
849
  fetchConfig,
383
- getRpcUrl
850
+ getRpcUrl,
851
+ isPaymentSettlementFailed,
852
+ isPaymentSettlementSucceeded,
853
+ isPaymentVerificationFailed,
854
+ isPaymentVerificationSucceeded,
855
+ parseCloudEvent
384
856
  });
385
857
  //# sourceMappingURL=index.js.map