@micro-cms/crypto-payments 1.0.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.
@@ -0,0 +1,54 @@
1
+ import * as react from 'react';
2
+ import react__default from 'react';
3
+ import { PaymentIntent, PaymentVerification } from '@micro-cms/types';
4
+
5
+ interface SolanaWindow extends Window {
6
+ solana?: {
7
+ isPhantom?: boolean;
8
+ connect: (options?: {
9
+ onlyIfTrusted?: boolean;
10
+ }) => Promise<{
11
+ publicKey: {
12
+ toString: () => string;
13
+ };
14
+ }>;
15
+ signTransaction: (transaction: any) => Promise<any>;
16
+ request: (request: {
17
+ method: string;
18
+ params?: any;
19
+ }) => Promise<any>;
20
+ };
21
+ }
22
+ declare const useSolanaWallet: () => {
23
+ isAvailable: boolean;
24
+ connect: () => Promise<string>;
25
+ sendPayment: (intent: PaymentIntent) => Promise<string>;
26
+ };
27
+
28
+ declare const PaymentWidget: react__default.FC<PaymentWidgetProps>;
29
+
30
+ interface PaymentWidgetProps {
31
+ orderId: string;
32
+ amount?: number;
33
+ currency?: string;
34
+ onSuccess?: (verification: PaymentVerification) => void;
35
+ onError?: (error: Error) => void;
36
+ endpoints?: {
37
+ initiate?: string;
38
+ verify?: string;
39
+ };
40
+ className?: string;
41
+ }
42
+ type PaymentStatus = 'idle' | 'connecting' | 'initiating' | 'pending_signature' | 'verifying' | 'success' | 'error';
43
+ declare const usePayment: (props: PaymentWidgetProps) => {
44
+ status: PaymentStatus;
45
+ intent: PaymentIntent | null;
46
+ error: string | null;
47
+ initiate: () => Promise<void>;
48
+ verify: (txHash: string) => Promise<void>;
49
+ setStatus: react.Dispatch<react.SetStateAction<PaymentStatus>>;
50
+ isSolanaAvailable: boolean;
51
+ handleSolanaPay: () => Promise<void>;
52
+ };
53
+
54
+ export { type PaymentStatus, PaymentWidget, type PaymentWidgetProps, type SolanaWindow, usePayment, useSolanaWallet };
@@ -0,0 +1,54 @@
1
+ import * as react from 'react';
2
+ import react__default from 'react';
3
+ import { PaymentIntent, PaymentVerification } from '@micro-cms/types';
4
+
5
+ interface SolanaWindow extends Window {
6
+ solana?: {
7
+ isPhantom?: boolean;
8
+ connect: (options?: {
9
+ onlyIfTrusted?: boolean;
10
+ }) => Promise<{
11
+ publicKey: {
12
+ toString: () => string;
13
+ };
14
+ }>;
15
+ signTransaction: (transaction: any) => Promise<any>;
16
+ request: (request: {
17
+ method: string;
18
+ params?: any;
19
+ }) => Promise<any>;
20
+ };
21
+ }
22
+ declare const useSolanaWallet: () => {
23
+ isAvailable: boolean;
24
+ connect: () => Promise<string>;
25
+ sendPayment: (intent: PaymentIntent) => Promise<string>;
26
+ };
27
+
28
+ declare const PaymentWidget: react__default.FC<PaymentWidgetProps>;
29
+
30
+ interface PaymentWidgetProps {
31
+ orderId: string;
32
+ amount?: number;
33
+ currency?: string;
34
+ onSuccess?: (verification: PaymentVerification) => void;
35
+ onError?: (error: Error) => void;
36
+ endpoints?: {
37
+ initiate?: string;
38
+ verify?: string;
39
+ };
40
+ className?: string;
41
+ }
42
+ type PaymentStatus = 'idle' | 'connecting' | 'initiating' | 'pending_signature' | 'verifying' | 'success' | 'error';
43
+ declare const usePayment: (props: PaymentWidgetProps) => {
44
+ status: PaymentStatus;
45
+ intent: PaymentIntent | null;
46
+ error: string | null;
47
+ initiate: () => Promise<void>;
48
+ verify: (txHash: string) => Promise<void>;
49
+ setStatus: react.Dispatch<react.SetStateAction<PaymentStatus>>;
50
+ isSolanaAvailable: boolean;
51
+ handleSolanaPay: () => Promise<void>;
52
+ };
53
+
54
+ export { type PaymentStatus, PaymentWidget, type PaymentWidgetProps, type SolanaWindow, usePayment, useSolanaWallet };
package/dist/index.js ADDED
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PaymentWidget: () => PaymentWidget,
24
+ usePayment: () => usePayment,
25
+ useSolanaWallet: () => useSolanaWallet
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var import_react = require("react");
29
+
30
+ // src/providers/SolanaProvider.ts
31
+ var useSolanaWallet = () => {
32
+ const getProvider = () => {
33
+ if ("solana" in window) {
34
+ const anyWindow = window;
35
+ if (anyWindow.solana?.isPhantom) {
36
+ return anyWindow.solana;
37
+ }
38
+ }
39
+ return void 0;
40
+ };
41
+ const connect = async () => {
42
+ const provider = getProvider();
43
+ if (provider) {
44
+ try {
45
+ const resp = await provider.connect();
46
+ return resp.publicKey.toString();
47
+ } catch (err) {
48
+ throw new Error("User rejected the connection");
49
+ }
50
+ } else {
51
+ throw new Error("Phantom wallet not found");
52
+ }
53
+ };
54
+ const sendPayment = async (intent) => {
55
+ const provider = getProvider();
56
+ if (!provider) throw new Error("Wallet not connected");
57
+ console.log(`Simulating Solana payment to ${intent.paymentAddress} for ${intent.amount} SOL`);
58
+ return "simulated_solana_signature_" + Math.random().toString(36).slice(2);
59
+ };
60
+ return { isAvailable: !!getProvider(), connect, sendPayment };
61
+ };
62
+
63
+ // src/PaymentWidget.tsx
64
+ var import_lucide_react = require("lucide-react");
65
+ var import_clsx = require("clsx");
66
+ var import_tailwind_merge = require("tailwind-merge");
67
+ var import_jsx_runtime = require("react/jsx-runtime");
68
+ function cn(...inputs) {
69
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
70
+ }
71
+ var PaymentWidget = ({
72
+ className,
73
+ ...props
74
+ }) => {
75
+ const { status, intent, error, initiate, verify, setStatus, isSolanaAvailable, handleSolanaPay } = usePayment(props);
76
+ if (status === "success") {
77
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("p-6 text-center bg-green-50 rounded-xl border border-green-200", className), children: [
78
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.CheckCircle, { className: "mx-auto w-12 h-12 text-green-500 mb-4" }),
79
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-lg font-semibold text-green-900", children: "Payment Successful" }),
80
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-green-700 mt-1", children: "Your order has been confirmed." })
81
+ ] });
82
+ }
83
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden", className), children: [
84
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-6", children: [
85
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-3 mb-6", children: [
86
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-2 bg-indigo-50 rounded-lg", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Wallet, { className: "w-5 h-5 text-indigo-600" }) }),
87
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
88
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "font-semibold text-slate-900", children: "Crypto Payment" }),
89
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm text-slate-500", children: "Pay securely using your wallet" })
90
+ ] })
91
+ ] }),
92
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-4 p-3 bg-red-50 border border-red-100 rounded-lg flex gap-3 text-red-700 text-sm", children: [
93
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.AlertCircle, { className: "w-4 h-4 shrink-0" }),
94
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: error })
95
+ ] }),
96
+ status === "idle" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
97
+ "button",
98
+ {
99
+ onClick: initiate,
100
+ className: "w-full py-3 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-xl transition-colors flex items-center justify-center gap-2",
101
+ children: "Start Payment"
102
+ }
103
+ ),
104
+ (status === "connecting" || status === "initiating" || status === "verifying") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "py-8 text-center", children: [
105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "w-8 h-8 text-indigo-600 animate-spin mx-auto mb-3" }),
106
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-slate-600 font-medium", children: status === "connecting" ? "Connecting wallet..." : status === "initiating" ? "Preparing transaction..." : "Verifying on-chain..." })
107
+ ] }),
108
+ status === "pending_signature" && intent && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-4", children: [
109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 bg-slate-50 rounded-xl space-y-2", children: [
110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex justify-between text-sm", children: [
111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-slate-500", children: "Amount to pay" }),
112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "font-mono font-medium text-slate-900", children: [
113
+ intent.amount,
114
+ " ",
115
+ intent.currency
116
+ ] })
117
+ ] }),
118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex justify-between text-sm", children: [
119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-slate-500", children: "Network" }),
120
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-slate-900 font-medium", children: intent.network })
121
+ ] })
122
+ ] }),
123
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-slate-500 uppercase font-bold tracking-wider", children: "Destination Address" }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-3 bg-slate-100 rounded-lg font-mono text-xs break-all text-slate-600 border border-slate-200", children: intent.paymentAddress })
126
+ ] }),
127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-2", children: intent.network.toLowerCase().includes("solana") && isSolanaAvailable ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
128
+ "button",
129
+ {
130
+ onClick: handleSolanaPay,
131
+ className: "w-full py-3 px-4 bg-[#512da8] hover:bg-[#4527a0] text-white font-medium rounded-xl transition-colors flex items-center justify-center gap-2",
132
+ children: [
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Cpu, { className: "w-4 h-4" }),
134
+ "Pay with Phantom"
135
+ ]
136
+ }
137
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
138
+ "button",
139
+ {
140
+ onClick: () => {
141
+ setStatus("verifying");
142
+ setTimeout(() => verify("0x_mock_transaction_hash_generic"), 2e3);
143
+ },
144
+ className: "w-full py-3 px-4 bg-slate-900 hover:bg-black text-white font-medium rounded-xl transition-colors flex items-center justify-center gap-2",
145
+ children: "Confirm in Wallet"
146
+ }
147
+ ) })
148
+ ] })
149
+ ] }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bg-slate-50 px-6 py-3 border-t border-slate-100 flex justify-between items-center", children: [
151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-slate-400", children: "Powered by Micro-CMS Crypto" }),
152
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: "#", className: "text-xs text-indigo-600 hover:text-indigo-700 font-medium flex items-center gap-1", children: [
153
+ "Help ",
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ExternalLink, { className: "w-3 h-3" })
155
+ ] })
156
+ ] })
157
+ ] });
158
+ };
159
+
160
+ // src/index.ts
161
+ var usePayment = (props) => {
162
+ const [status, setStatus] = (0, import_react.useState)("idle");
163
+ const [error, setError] = (0, import_react.useState)(null);
164
+ const [intent, setIntent] = (0, import_react.useState)(null);
165
+ const initiate = (0, import_react.useCallback)(async () => {
166
+ try {
167
+ setStatus("initiating");
168
+ setError(null);
169
+ const response = await fetch(props.endpoints?.initiate || "/api/orders/initiate", {
170
+ method: "POST",
171
+ headers: { "Content-Type": "application/json" },
172
+ body: JSON.stringify({
173
+ orderId: props.orderId,
174
+ amount: props.amount,
175
+ currency: props.currency
176
+ })
177
+ });
178
+ if (!response.ok) {
179
+ const errorData = await response.json();
180
+ throw new Error(errorData.error || "Failed to initiate payment intent");
181
+ }
182
+ const data = await response.json();
183
+ setIntent(data);
184
+ setStatus("pending_signature");
185
+ } catch (err) {
186
+ setError(err.message);
187
+ setStatus("error");
188
+ props.onError?.(err);
189
+ }
190
+ }, [props.endpoints?.initiate, props.orderId, props.amount, props.currency, props.onError]);
191
+ const verify = (0, import_react.useCallback)(async (txHash) => {
192
+ try {
193
+ setStatus("verifying");
194
+ const response = await fetch(props.endpoints?.verify || "/api/orders/verify-payment", {
195
+ method: "POST",
196
+ headers: { "Content-Type": "application/json" },
197
+ body: JSON.stringify({
198
+ orderId: props.orderId,
199
+ transactionHash: txHash
200
+ })
201
+ });
202
+ if (!response.ok) {
203
+ const errorData = await response.json();
204
+ throw new Error(errorData.error || "Payment verification failed");
205
+ }
206
+ const data = await response.json();
207
+ if (data.status === "confirmed") {
208
+ setStatus("success");
209
+ props.onSuccess?.(data);
210
+ } else {
211
+ throw new Error("Payment not confirmed yet, status: " + data.status);
212
+ }
213
+ } catch (err) {
214
+ setError(err.message);
215
+ setStatus("error");
216
+ props.onError?.(err);
217
+ }
218
+ }, [props.endpoints?.verify, props.orderId, props.onSuccess, props.onError]);
219
+ const { isAvailable: isSolanaAvailable, connect: connectSolana, sendPayment: sendSolanaPayment } = useSolanaWallet();
220
+ const handleSolanaPay = async () => {
221
+ try {
222
+ if (!intent) {
223
+ setError("Payment intent not established.");
224
+ setStatus("error");
225
+ return;
226
+ }
227
+ setStatus("connecting");
228
+ const publicKey = await connectSolana();
229
+ console.log("Connected to Solana with public key:", publicKey);
230
+ setStatus("pending_signature");
231
+ const signature = await sendSolanaPayment(intent);
232
+ setStatus("verifying");
233
+ await verify(signature);
234
+ } catch (err) {
235
+ setError(err.message || "Failed to connect to Phantom or sign transaction.");
236
+ setStatus("error");
237
+ props.onError?.(err instanceof Error ? err : new Error(String(err)));
238
+ }
239
+ };
240
+ return { status, intent, error, initiate, verify, setStatus, isSolanaAvailable, handleSolanaPay };
241
+ };
242
+ // Annotate the CommonJS export names for ESM import in node:
243
+ 0 && (module.exports = {
244
+ PaymentWidget,
245
+ usePayment,
246
+ useSolanaWallet
247
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,220 @@
1
+ // src/index.ts
2
+ import { useState, useCallback } from "react";
3
+
4
+ // src/providers/SolanaProvider.ts
5
+ var useSolanaWallet = () => {
6
+ const getProvider = () => {
7
+ if ("solana" in window) {
8
+ const anyWindow = window;
9
+ if (anyWindow.solana?.isPhantom) {
10
+ return anyWindow.solana;
11
+ }
12
+ }
13
+ return void 0;
14
+ };
15
+ const connect = async () => {
16
+ const provider = getProvider();
17
+ if (provider) {
18
+ try {
19
+ const resp = await provider.connect();
20
+ return resp.publicKey.toString();
21
+ } catch (err) {
22
+ throw new Error("User rejected the connection");
23
+ }
24
+ } else {
25
+ throw new Error("Phantom wallet not found");
26
+ }
27
+ };
28
+ const sendPayment = async (intent) => {
29
+ const provider = getProvider();
30
+ if (!provider) throw new Error("Wallet not connected");
31
+ console.log(`Simulating Solana payment to ${intent.paymentAddress} for ${intent.amount} SOL`);
32
+ return "simulated_solana_signature_" + Math.random().toString(36).slice(2);
33
+ };
34
+ return { isAvailable: !!getProvider(), connect, sendPayment };
35
+ };
36
+
37
+ // src/PaymentWidget.tsx
38
+ import { Wallet, CheckCircle, Loader2, AlertCircle, ExternalLink, Cpu } from "lucide-react";
39
+ import { clsx } from "clsx";
40
+ import { twMerge } from "tailwind-merge";
41
+ import { jsx, jsxs } from "react/jsx-runtime";
42
+ function cn(...inputs) {
43
+ return twMerge(clsx(inputs));
44
+ }
45
+ var PaymentWidget = ({
46
+ className,
47
+ ...props
48
+ }) => {
49
+ const { status, intent, error, initiate, verify, setStatus, isSolanaAvailable, handleSolanaPay } = usePayment(props);
50
+ if (status === "success") {
51
+ return /* @__PURE__ */ jsxs("div", { className: cn("p-6 text-center bg-green-50 rounded-xl border border-green-200", className), children: [
52
+ /* @__PURE__ */ jsx(CheckCircle, { className: "mx-auto w-12 h-12 text-green-500 mb-4" }),
53
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-green-900", children: "Payment Successful" }),
54
+ /* @__PURE__ */ jsx("p", { className: "text-green-700 mt-1", children: "Your order has been confirmed." })
55
+ ] });
56
+ }
57
+ return /* @__PURE__ */ jsxs("div", { className: cn("bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden", className), children: [
58
+ /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
59
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-6", children: [
60
+ /* @__PURE__ */ jsx("div", { className: "p-2 bg-indigo-50 rounded-lg", children: /* @__PURE__ */ jsx(Wallet, { className: "w-5 h-5 text-indigo-600" }) }),
61
+ /* @__PURE__ */ jsxs("div", { children: [
62
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-slate-900", children: "Crypto Payment" }),
63
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Pay securely using your wallet" })
64
+ ] })
65
+ ] }),
66
+ error && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-red-50 border border-red-100 rounded-lg flex gap-3 text-red-700 text-sm", children: [
67
+ /* @__PURE__ */ jsx(AlertCircle, { className: "w-4 h-4 shrink-0" }),
68
+ /* @__PURE__ */ jsx("p", { children: error })
69
+ ] }),
70
+ status === "idle" && /* @__PURE__ */ jsx(
71
+ "button",
72
+ {
73
+ onClick: initiate,
74
+ className: "w-full py-3 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-xl transition-colors flex items-center justify-center gap-2",
75
+ children: "Start Payment"
76
+ }
77
+ ),
78
+ (status === "connecting" || status === "initiating" || status === "verifying") && /* @__PURE__ */ jsxs("div", { className: "py-8 text-center", children: [
79
+ /* @__PURE__ */ jsx(Loader2, { className: "w-8 h-8 text-indigo-600 animate-spin mx-auto mb-3" }),
80
+ /* @__PURE__ */ jsx("p", { className: "text-slate-600 font-medium", children: status === "connecting" ? "Connecting wallet..." : status === "initiating" ? "Preparing transaction..." : "Verifying on-chain..." })
81
+ ] }),
82
+ status === "pending_signature" && intent && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
83
+ /* @__PURE__ */ jsxs("div", { className: "p-4 bg-slate-50 rounded-xl space-y-2", children: [
84
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm", children: [
85
+ /* @__PURE__ */ jsx("span", { className: "text-slate-500", children: "Amount to pay" }),
86
+ /* @__PURE__ */ jsxs("span", { className: "font-mono font-medium text-slate-900", children: [
87
+ intent.amount,
88
+ " ",
89
+ intent.currency
90
+ ] })
91
+ ] }),
92
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm", children: [
93
+ /* @__PURE__ */ jsx("span", { className: "text-slate-500", children: "Network" }),
94
+ /* @__PURE__ */ jsx("span", { className: "text-slate-900 font-medium", children: intent.network })
95
+ ] })
96
+ ] }),
97
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
98
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500 uppercase font-bold tracking-wider", children: "Destination Address" }),
99
+ /* @__PURE__ */ jsx("div", { className: "p-3 bg-slate-100 rounded-lg font-mono text-xs break-all text-slate-600 border border-slate-200", children: intent.paymentAddress })
100
+ ] }),
101
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: intent.network.toLowerCase().includes("solana") && isSolanaAvailable ? /* @__PURE__ */ jsxs(
102
+ "button",
103
+ {
104
+ onClick: handleSolanaPay,
105
+ className: "w-full py-3 px-4 bg-[#512da8] hover:bg-[#4527a0] text-white font-medium rounded-xl transition-colors flex items-center justify-center gap-2",
106
+ children: [
107
+ /* @__PURE__ */ jsx(Cpu, { className: "w-4 h-4" }),
108
+ "Pay with Phantom"
109
+ ]
110
+ }
111
+ ) : /* @__PURE__ */ jsx(
112
+ "button",
113
+ {
114
+ onClick: () => {
115
+ setStatus("verifying");
116
+ setTimeout(() => verify("0x_mock_transaction_hash_generic"), 2e3);
117
+ },
118
+ className: "w-full py-3 px-4 bg-slate-900 hover:bg-black text-white font-medium rounded-xl transition-colors flex items-center justify-center gap-2",
119
+ children: "Confirm in Wallet"
120
+ }
121
+ ) })
122
+ ] })
123
+ ] }),
124
+ /* @__PURE__ */ jsxs("div", { className: "bg-slate-50 px-6 py-3 border-t border-slate-100 flex justify-between items-center", children: [
125
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-400", children: "Powered by Micro-CMS Crypto" }),
126
+ /* @__PURE__ */ jsxs("a", { href: "#", className: "text-xs text-indigo-600 hover:text-indigo-700 font-medium flex items-center gap-1", children: [
127
+ "Help ",
128
+ /* @__PURE__ */ jsx(ExternalLink, { className: "w-3 h-3" })
129
+ ] })
130
+ ] })
131
+ ] });
132
+ };
133
+
134
+ // src/index.ts
135
+ var usePayment = (props) => {
136
+ const [status, setStatus] = useState("idle");
137
+ const [error, setError] = useState(null);
138
+ const [intent, setIntent] = useState(null);
139
+ const initiate = useCallback(async () => {
140
+ try {
141
+ setStatus("initiating");
142
+ setError(null);
143
+ const response = await fetch(props.endpoints?.initiate || "/api/orders/initiate", {
144
+ method: "POST",
145
+ headers: { "Content-Type": "application/json" },
146
+ body: JSON.stringify({
147
+ orderId: props.orderId,
148
+ amount: props.amount,
149
+ currency: props.currency
150
+ })
151
+ });
152
+ if (!response.ok) {
153
+ const errorData = await response.json();
154
+ throw new Error(errorData.error || "Failed to initiate payment intent");
155
+ }
156
+ const data = await response.json();
157
+ setIntent(data);
158
+ setStatus("pending_signature");
159
+ } catch (err) {
160
+ setError(err.message);
161
+ setStatus("error");
162
+ props.onError?.(err);
163
+ }
164
+ }, [props.endpoints?.initiate, props.orderId, props.amount, props.currency, props.onError]);
165
+ const verify = useCallback(async (txHash) => {
166
+ try {
167
+ setStatus("verifying");
168
+ const response = await fetch(props.endpoints?.verify || "/api/orders/verify-payment", {
169
+ method: "POST",
170
+ headers: { "Content-Type": "application/json" },
171
+ body: JSON.stringify({
172
+ orderId: props.orderId,
173
+ transactionHash: txHash
174
+ })
175
+ });
176
+ if (!response.ok) {
177
+ const errorData = await response.json();
178
+ throw new Error(errorData.error || "Payment verification failed");
179
+ }
180
+ const data = await response.json();
181
+ if (data.status === "confirmed") {
182
+ setStatus("success");
183
+ props.onSuccess?.(data);
184
+ } else {
185
+ throw new Error("Payment not confirmed yet, status: " + data.status);
186
+ }
187
+ } catch (err) {
188
+ setError(err.message);
189
+ setStatus("error");
190
+ props.onError?.(err);
191
+ }
192
+ }, [props.endpoints?.verify, props.orderId, props.onSuccess, props.onError]);
193
+ const { isAvailable: isSolanaAvailable, connect: connectSolana, sendPayment: sendSolanaPayment } = useSolanaWallet();
194
+ const handleSolanaPay = async () => {
195
+ try {
196
+ if (!intent) {
197
+ setError("Payment intent not established.");
198
+ setStatus("error");
199
+ return;
200
+ }
201
+ setStatus("connecting");
202
+ const publicKey = await connectSolana();
203
+ console.log("Connected to Solana with public key:", publicKey);
204
+ setStatus("pending_signature");
205
+ const signature = await sendSolanaPayment(intent);
206
+ setStatus("verifying");
207
+ await verify(signature);
208
+ } catch (err) {
209
+ setError(err.message || "Failed to connect to Phantom or sign transaction.");
210
+ setStatus("error");
211
+ props.onError?.(err instanceof Error ? err : new Error(String(err)));
212
+ }
213
+ };
214
+ return { status, intent, error, initiate, verify, setStatus, isSolanaAvailable, handleSolanaPay };
215
+ };
216
+ export {
217
+ PaymentWidget,
218
+ usePayment,
219
+ useSolanaWallet
220
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@micro-cms/crypto-payments",
3
+ "version": "1.0.0",
4
+ "description": "Composable Crypto Payment Widget for Micro-CMS",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts",
13
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
14
+ "lint": "eslint src/**/*.ts"
15
+ },
16
+ "dependencies": {
17
+ "lucide-react": "^0.344.0",
18
+ "clsx": "^2.1.0",
19
+ "tailwind-merge": "^2.2.1"
20
+ },
21
+ "peerDependencies": {
22
+ "react": "^18.0.0",
23
+ "@micro-cms/types": "workspace:*",
24
+ "@micro-cms/core": "workspace:*"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^18.2.0",
28
+ "typescript": "^5.3.3",
29
+ "tsup": "^8.0.2"
30
+ }
31
+ }