@moneymq/react 0.2.0 → 0.2.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 MoneyMQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.mts CHANGED
@@ -54,13 +54,17 @@ interface Product {
54
54
  name: string;
55
55
  description?: string;
56
56
  }
57
+ interface BasketItem {
58
+ /** Product details */
59
+ product: Product;
60
+ /** Price details */
61
+ price: Price;
62
+ /** Quantity (defaults to 1) */
63
+ quantity?: number;
64
+ }
57
65
  interface PayButtonProps {
58
- /** Price ID to fetch details from API */
59
- priceId?: string;
60
- /** Price object (alternative to priceId) */
61
- price?: Price;
62
- /** Product object */
63
- product?: Product;
66
+ /** Basket of items to purchase */
67
+ basket: BasketItem[];
64
68
  onSuccess?: (payment: Payment$1) => void;
65
69
  onError?: (error: Error) => void;
66
70
  variant?: 'solid' | 'outline';
@@ -69,18 +73,32 @@ interface PayButtonProps {
69
73
  }
70
74
  declare const PayButton: React.ForwardRefExoticComponent<PayButtonProps & React.RefAttributes<HTMLButtonElement>>;
71
75
 
76
+ interface LineItem {
77
+ product: {
78
+ id: string;
79
+ name: string;
80
+ description?: string;
81
+ };
82
+ price: {
83
+ id: string;
84
+ unit_amount: number;
85
+ currency: string;
86
+ };
87
+ quantity: number;
88
+ subtotal: number;
89
+ }
72
90
  interface PaymentModalProps {
73
91
  visible: boolean;
74
92
  onClose: () => void;
75
93
  amount: number;
76
94
  currency: string;
77
95
  recipient: string;
78
- productName?: string;
96
+ lineItems?: LineItem[];
79
97
  onSuccess?: (signature: string) => void;
80
98
  onError?: (error: Error) => void;
81
99
  accentColor?: string;
82
100
  }
83
- declare function PaymentModal({ visible, onClose, amount, currency, recipient, productName, onSuccess, onError, accentColor, }: PaymentModalProps): react_jsx_runtime.JSX.Element | null;
101
+ declare function PaymentModal({ visible, onClose, amount, currency, recipient, lineItems, onSuccess, onError, accentColor, }: PaymentModalProps): react_jsx_runtime.JSX.Element | null;
84
102
 
85
103
  interface Payment {
86
104
  id: string;
@@ -96,4 +114,4 @@ interface UsePaymentReturn {
96
114
  }
97
115
  declare function usePayment(): UsePaymentReturn;
98
116
 
99
- export { type Branding, type MoneyMQClient, MoneyMQProvider, type MoneyMQProviderProps, PayButton, type PayButtonProps, type Payment$1 as Payment, PaymentModal, type PaymentModalProps, type Price, type Product, type SandboxAccount, type UsePaymentReturn, useMoneyMQ, usePayment, useSandbox };
117
+ export { type BasketItem, type Branding, type LineItem, type MoneyMQClient, MoneyMQProvider, type MoneyMQProviderProps, PayButton, type PayButtonProps, type Payment$1 as Payment, PaymentModal, type PaymentModalProps, type Price, type Product, type SandboxAccount, type UsePaymentReturn, useMoneyMQ, usePayment, useSandbox };
package/dist/index.d.ts CHANGED
@@ -54,13 +54,17 @@ interface Product {
54
54
  name: string;
55
55
  description?: string;
56
56
  }
57
+ interface BasketItem {
58
+ /** Product details */
59
+ product: Product;
60
+ /** Price details */
61
+ price: Price;
62
+ /** Quantity (defaults to 1) */
63
+ quantity?: number;
64
+ }
57
65
  interface PayButtonProps {
58
- /** Price ID to fetch details from API */
59
- priceId?: string;
60
- /** Price object (alternative to priceId) */
61
- price?: Price;
62
- /** Product object */
63
- product?: Product;
66
+ /** Basket of items to purchase */
67
+ basket: BasketItem[];
64
68
  onSuccess?: (payment: Payment$1) => void;
65
69
  onError?: (error: Error) => void;
66
70
  variant?: 'solid' | 'outline';
@@ -69,18 +73,32 @@ interface PayButtonProps {
69
73
  }
70
74
  declare const PayButton: React.ForwardRefExoticComponent<PayButtonProps & React.RefAttributes<HTMLButtonElement>>;
71
75
 
76
+ interface LineItem {
77
+ product: {
78
+ id: string;
79
+ name: string;
80
+ description?: string;
81
+ };
82
+ price: {
83
+ id: string;
84
+ unit_amount: number;
85
+ currency: string;
86
+ };
87
+ quantity: number;
88
+ subtotal: number;
89
+ }
72
90
  interface PaymentModalProps {
73
91
  visible: boolean;
74
92
  onClose: () => void;
75
93
  amount: number;
76
94
  currency: string;
77
95
  recipient: string;
78
- productName?: string;
96
+ lineItems?: LineItem[];
79
97
  onSuccess?: (signature: string) => void;
80
98
  onError?: (error: Error) => void;
81
99
  accentColor?: string;
82
100
  }
83
- declare function PaymentModal({ visible, onClose, amount, currency, recipient, productName, onSuccess, onError, accentColor, }: PaymentModalProps): react_jsx_runtime.JSX.Element | null;
101
+ declare function PaymentModal({ visible, onClose, amount, currency, recipient, lineItems, onSuccess, onError, accentColor, }: PaymentModalProps): react_jsx_runtime.JSX.Element | null;
84
102
 
85
103
  interface Payment {
86
104
  id: string;
@@ -96,4 +114,4 @@ interface UsePaymentReturn {
96
114
  }
97
115
  declare function usePayment(): UsePaymentReturn;
98
116
 
99
- export { type Branding, type MoneyMQClient, MoneyMQProvider, type MoneyMQProviderProps, PayButton, type PayButtonProps, type Payment$1 as Payment, PaymentModal, type PaymentModalProps, type Price, type Product, type SandboxAccount, type UsePaymentReturn, useMoneyMQ, usePayment, useSandbox };
117
+ export { type BasketItem, type Branding, type LineItem, type MoneyMQClient, MoneyMQProvider, type MoneyMQProviderProps, PayButton, type PayButtonProps, type Payment$1 as Payment, PaymentModal, type PaymentModalProps, type Price, type Product, type SandboxAccount, type UsePaymentReturn, useMoneyMQ, usePayment, useSandbox };
package/dist/index.js CHANGED
@@ -436,7 +436,7 @@ function MoneyMQProvider({
436
436
  }
437
437
 
438
438
  // src/pay-button.tsx
439
- var import_react6 = require("react");
439
+ var import_react6 = __toESM(require("react"));
440
440
 
441
441
  // src/payment-modal.tsx
442
442
  var import_react4 = require("react");
@@ -522,8 +522,9 @@ async function makeRequestWith402Handling(url, method, body, secretKeyHex, rpcUr
522
522
  }
523
523
  return data;
524
524
  }
525
- async function createSandboxPayment(apiUrl, rpcUrl, amount, currency, recipient, senderAddress, secretKeyHex, productName) {
525
+ async function createSandboxPayment(apiUrl, rpcUrl, amount, currency, recipient, senderAddress, secretKeyHex, lineItems) {
526
526
  console.log("[MoneyMQ] Creating sandbox payment...", { amount, currency, recipient, senderAddress });
527
+ const description = lineItems && lineItems.length > 0 ? `Purchase - ${lineItems.map((item) => item.product.name).join(", ")}` : "Payment";
527
528
  const paymentIntent = await makeRequestWith402Handling(
528
529
  `${apiUrl}/catalog/v1/payment_intents`,
529
530
  "POST",
@@ -532,7 +533,7 @@ async function createSandboxPayment(apiUrl, rpcUrl, amount, currency, recipient,
532
533
  // Convert to cents (Stripe-style)
533
534
  currency: currency.toLowerCase(),
534
535
  customer: senderAddress,
535
- description: productName ? `Purchase - ${productName}` : "Payment",
536
+ description,
536
537
  metadata: {
537
538
  sender_address: senderAddress,
538
539
  recipient_address: recipient
@@ -563,7 +564,7 @@ function PaymentModal({
563
564
  amount,
564
565
  currency,
565
566
  recipient,
566
- productName,
567
+ lineItems,
567
568
  onSuccess,
568
569
  onError,
569
570
  accentColor = "#ec4899"
@@ -661,7 +662,7 @@ function PaymentModal({
661
662
  recipient,
662
663
  senderAddress,
663
664
  secretKeyHex,
664
- productName
665
+ lineItems
665
666
  );
666
667
  setIsSending(false);
667
668
  onSuccess?.(paymentId);
@@ -709,7 +710,7 @@ function PaymentModal({
709
710
  setIsSending(false);
710
711
  onError?.(err instanceof Error ? err : new Error(String(err)));
711
712
  }
712
- }, [publicKey, recipient, amount, currency, onSuccess, onError, onClose, selectedPaymentMethod, client.config.endpoint, productName]);
713
+ }, [publicKey, recipient, amount, currency, onSuccess, onError, onClose, selectedPaymentMethod, client.config.endpoint, lineItems]);
713
714
  const canPay = (connected && publicKey || selectedPaymentMethod?.type === "sandbox_account") && recipient && !isSending;
714
715
  if (!visible) return null;
715
716
  const WalletIcon = () => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
@@ -1198,7 +1199,38 @@ function PaymentModal({
1198
1199
  },
1199
1200
  children: [
1200
1201
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginBottom: "1rem" }, children: [
1201
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "0.875rem", color: "#8e8e93", marginBottom: "0.25rem" }, children: productName ? `Pay ${productName}` : "Total" }),
1202
+ lineItems && lineItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { marginBottom: "0.75rem" }, children: lineItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1203
+ "div",
1204
+ {
1205
+ style: {
1206
+ display: "flex",
1207
+ justifyContent: "space-between",
1208
+ alignItems: "center",
1209
+ padding: "0.5rem 0",
1210
+ borderBottom: index < lineItems.length - 1 ? "1px solid #3a3a3c" : "none"
1211
+ },
1212
+ children: [
1213
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { flex: 1 }, children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "0.875rem", color: "#fff", fontWeight: 500 }, children: item.product.name }),
1215
+ item.quantity > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: "0.75rem", color: "#8e8e93" }, children: [
1216
+ "Qty: ",
1217
+ item.quantity,
1218
+ " \xD7 ",
1219
+ (item.price.unit_amount / 100).toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
1220
+ " ",
1221
+ item.price.currency.toUpperCase()
1222
+ ] })
1223
+ ] }),
1224
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: "0.875rem", color: "#fff", fontWeight: 500 }, children: [
1225
+ item.subtotal.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
1226
+ " ",
1227
+ item.price.currency.toUpperCase()
1228
+ ] })
1229
+ ]
1230
+ },
1231
+ item.product.id + "-" + index
1232
+ )) }),
1233
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: "0.875rem", color: "#8e8e93", marginBottom: "0.25rem" }, children: "Total" }),
1202
1234
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1203
1235
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", alignItems: "baseline", gap: "0.375rem" }, children: [
1204
1236
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: "2rem", fontWeight: 600, color: "#fff" }, children: amount.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }),
@@ -1367,9 +1399,7 @@ var outlineStyle = {
1367
1399
  };
1368
1400
  var PayButton = (0, import_react6.forwardRef)(
1369
1401
  function PayButton2({
1370
- priceId,
1371
- price: priceObject,
1372
- product: productObject,
1402
+ basket,
1373
1403
  onSuccess,
1374
1404
  onError,
1375
1405
  variant = "solid",
@@ -1379,18 +1409,35 @@ var PayButton = (0, import_react6.forwardRef)(
1379
1409
  const client = useMoneyMQ();
1380
1410
  const [isModalOpen, setIsModalOpen] = (0, import_react6.useState)(false);
1381
1411
  const [isHovered, setIsHovered] = (0, import_react6.useState)(false);
1382
- const hasPriceObject = priceObject !== void 0;
1383
- const [isLoading, setIsLoading] = (0, import_react6.useState)(!hasPriceObject);
1412
+ const [isLoading, setIsLoading] = (0, import_react6.useState)(true);
1384
1413
  const [error, setError] = (0, import_react6.useState)(null);
1385
- const [amount, setAmount] = (0, import_react6.useState)(hasPriceObject ? priceObject.unit_amount / 100 : 0);
1386
- const [currency, setCurrency] = (0, import_react6.useState)(hasPriceObject ? priceObject.currency.toUpperCase() : "USDC");
1387
1414
  const [recipient, setRecipient] = (0, import_react6.useState)("");
1388
- const [productName, setProductName] = (0, import_react6.useState)(productObject?.name);
1415
+ const { totalAmount, currency, lineItems } = import_react6.default.useMemo(() => {
1416
+ if (!basket || basket.length === 0) {
1417
+ return { totalAmount: 0, currency: "USDC", lineItems: [] };
1418
+ }
1419
+ const baseCurrency = basket[0].price.currency.toUpperCase();
1420
+ const items = basket.map((item) => ({
1421
+ product: item.product,
1422
+ price: item.price,
1423
+ quantity: item.quantity ?? 1,
1424
+ subtotal: item.price.unit_amount / 100 * (item.quantity ?? 1)
1425
+ }));
1426
+ const total = items.reduce((sum, item) => sum + item.subtotal, 0);
1427
+ return {
1428
+ totalAmount: total,
1429
+ currency: baseCurrency,
1430
+ lineItems: items
1431
+ };
1432
+ }, [basket]);
1389
1433
  (0, import_react6.useEffect)(() => {
1390
1434
  async function fetchPaymentDetails() {
1391
1435
  setIsLoading(true);
1392
1436
  setError(null);
1393
1437
  try {
1438
+ if (!basket || basket.length === 0) {
1439
+ throw new Error("Basket is empty");
1440
+ }
1394
1441
  const apiUrl = client.config.endpoint;
1395
1442
  const configResponse = await fetch(`${apiUrl}/config`);
1396
1443
  if (!configResponse.ok) {
@@ -1400,42 +1447,6 @@ var PayButton = (0, import_react6.forwardRef)(
1400
1447
  if (config.x402?.payoutAccount?.address) {
1401
1448
  setRecipient(config.x402.payoutAccount.address);
1402
1449
  }
1403
- if (hasPriceObject) {
1404
- setAmount(priceObject.unit_amount / 100);
1405
- setCurrency(priceObject.currency.toUpperCase());
1406
- if (productObject) {
1407
- setProductName(productObject.name);
1408
- } else if (priceObject.product) {
1409
- try {
1410
- const productResponse = await fetch(`${apiUrl}/catalog/v1/products/${priceObject.product}`);
1411
- if (productResponse.ok) {
1412
- const product = await productResponse.json();
1413
- setProductName(product.name);
1414
- }
1415
- } catch {
1416
- }
1417
- }
1418
- } else if (priceId) {
1419
- const priceResponse = await fetch(`${apiUrl}/catalog/v1/prices/${priceId}`);
1420
- if (!priceResponse.ok) {
1421
- throw new Error(`Failed to fetch price: ${priceResponse.status}`);
1422
- }
1423
- const price = await priceResponse.json();
1424
- setAmount(price.unit_amount / 100);
1425
- setCurrency(price.currency.toUpperCase());
1426
- if (price.product) {
1427
- try {
1428
- const productResponse = await fetch(`${apiUrl}/catalog/v1/products/${price.product}`);
1429
- if (productResponse.ok) {
1430
- const product = await productResponse.json();
1431
- setProductName(product.name);
1432
- }
1433
- } catch {
1434
- }
1435
- }
1436
- } else {
1437
- throw new Error("Either priceId or price object is required");
1438
- }
1439
1450
  } catch (err) {
1440
1451
  console.error("[PayButton] Error fetching payment details:", err);
1441
1452
  const errorMessage = err instanceof Error ? err.message : "Failed to load payment details";
@@ -1446,7 +1457,7 @@ var PayButton = (0, import_react6.forwardRef)(
1446
1457
  }
1447
1458
  }
1448
1459
  fetchPaymentDetails();
1449
- }, [priceId, priceObject, productObject, client.config.endpoint, onError, hasPriceObject]);
1460
+ }, [basket, client.config.endpoint, onError]);
1450
1461
  const handleClick = () => {
1451
1462
  if (!isLoading && !error) {
1452
1463
  setIsModalOpen(true);
@@ -1455,7 +1466,7 @@ var PayButton = (0, import_react6.forwardRef)(
1455
1466
  const handlePaymentSuccess = (signature) => {
1456
1467
  const payment = {
1457
1468
  id: `pay_${Date.now()}`,
1458
- amount,
1469
+ amount: totalAmount,
1459
1470
  currency,
1460
1471
  status: "completed",
1461
1472
  signature
@@ -1491,10 +1502,10 @@ var PayButton = (0, import_react6.forwardRef)(
1491
1502
  {
1492
1503
  visible: isModalOpen,
1493
1504
  onClose: () => setIsModalOpen(false),
1494
- amount,
1505
+ amount: totalAmount,
1495
1506
  currency,
1496
1507
  recipient,
1497
- productName,
1508
+ lineItems,
1498
1509
  onSuccess: handlePaymentSuccess,
1499
1510
  onError: handlePaymentError
1500
1511
  }