@voyage_ai/v402-web-ts 0.1.2 → 0.1.4

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.
@@ -1,168 +1 @@
1
- /**
2
- * x402 React Components - Default Styles
3
- *
4
- * ⚠️ DEPRECATED: This CSS file is no longer needed!
5
- *
6
- * All styles are now inline within the components.
7
- * You can safely remove any imports of this file:
8
- * - import '@voyage_ai/v402-web-ts/react/styles.css' ❌
9
- * - import '@voyage_ai/v402-web-ts/styles.css' ❌
10
- *
11
- * Just use the components directly - styles are built-in! ✅
12
- */
13
-
14
- .x402-wallet-connect {
15
- width: 100%;
16
- max-width: 500px;
17
- margin: 0 auto;
18
- }
19
-
20
- .x402-wallet-section,
21
- .x402-wallet-info {
22
- padding: 2rem;
23
- border: 1px solid #e0e0e0;
24
- border-radius: 8px;
25
- background: #ffffff;
26
- }
27
-
28
- .x402-section-title {
29
- margin: 0 0 1.5rem 0;
30
- font-size: 1.5rem;
31
- font-weight: 600;
32
- text-align: center;
33
- }
34
-
35
- .x402-wallet-buttons {
36
- display: flex;
37
- flex-direction: column;
38
- gap: 1rem;
39
- }
40
-
41
- .x402-wallet-option {
42
- display: flex;
43
- flex-direction: column;
44
- gap: 0.5rem;
45
- }
46
-
47
- .x402-connect-button,
48
- .x402-disconnect-button,
49
- .x402-pay-button {
50
- padding: 0.75rem 1.5rem;
51
- font-size: 1rem;
52
- font-weight: 500;
53
- border: none;
54
- border-radius: 6px;
55
- cursor: pointer;
56
- transition: all 0.2s;
57
- }
58
-
59
- .x402-connect-button {
60
- background: #0070f3;
61
- color: white;
62
- }
63
-
64
- .x402-connect-button:hover:not(:disabled) {
65
- background: #0051cc;
66
- }
67
-
68
- .x402-connect-button:disabled {
69
- background: #ccc;
70
- cursor: not-allowed;
71
- }
72
-
73
- .x402-disconnect-button {
74
- background: #ff4444;
75
- color: white;
76
- }
77
-
78
- .x402-disconnect-button:hover {
79
- background: #cc0000;
80
- }
81
-
82
- .x402-pay-button {
83
- background: #00d084;
84
- color: white;
85
- width: 100%;
86
- }
87
-
88
- .x402-pay-button:hover:not(:disabled) {
89
- background: #00a869;
90
- }
91
-
92
- .x402-pay-button:disabled {
93
- background: #ccc;
94
- cursor: not-allowed;
95
- }
96
-
97
- .x402-install-link {
98
- display: inline-block;
99
- padding: 0.5rem;
100
- font-size: 0.875rem;
101
- color: #0070f3;
102
- text-decoration: none;
103
- text-align: center;
104
- }
105
-
106
- .x402-install-link:hover {
107
- text-decoration: underline;
108
- }
109
-
110
- .x402-wallet-address {
111
- display: flex;
112
- flex-direction: column;
113
- gap: 0.5rem;
114
- margin-bottom: 1rem;
115
- }
116
-
117
- .x402-wallet-label {
118
- font-size: 0.875rem;
119
- color: #666;
120
- }
121
-
122
- .x402-address {
123
- font-family: monospace;
124
- font-size: 1rem;
125
- font-weight: 500;
126
- }
127
-
128
- .x402-wallet-actions {
129
- margin: 1rem 0;
130
- }
131
-
132
- .x402-hint {
133
- margin-top: 1rem;
134
- font-size: 0.875rem;
135
- color: #666;
136
- text-align: center;
137
- }
138
-
139
- .x402-error {
140
- margin-top: 1rem;
141
- padding: 0.75rem;
142
- background: #ffe0e0;
143
- color: #cc0000;
144
- border-radius: 4px;
145
- font-size: 0.875rem;
146
- }
147
-
148
- /* Dark mode support */
149
- @media (prefers-color-scheme: dark) {
150
- .x402-wallet-section,
151
- .x402-wallet-info {
152
- background: #1a1a1a;
153
- border-color: #333;
154
- }
155
-
156
- .x402-section-title {
157
- color: #fff;
158
- }
159
-
160
- .x402-wallet-label {
161
- color: #aaa;
162
- }
163
-
164
- .x402-hint {
165
- color: #aaa;
166
- }
167
- }
168
-
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-screen{height:100vh}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.min-w-0{min-width:0}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.overflow-hidden{overflow:hidden}.break-all{word-break:break-all}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.bg-black{background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-black,.bg-gray-50{--tw-bg-opacity:1}.bg-gray-50{background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.text-blue-600{color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-blue-600,.text-white{--tw-text-opacity:1}.text-white{color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.opacity-40{opacity:.4}.outline{outline-style:solid}.blur-xl{--tw-blur:blur(24px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:text-blue-700:hover{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity,1))}
package/package.json CHANGED
@@ -1,28 +1,38 @@
1
1
  {
2
2
  "name": "@voyage_ai/v402-web-ts",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "v402pay platform frontend SDK for seamless Web3 payment integration with Solana and Ethereum support",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./dist/index.d.ts",
10
11
  "import": "./dist/index.mjs",
11
- "require": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
12
+ "require": "./dist/index.js"
13
13
  },
14
14
  "./react": {
15
+ "types": "./dist/react/index.d.ts",
16
+ "style": "./dist/react/styles.css",
15
17
  "import": "./dist/react/index.mjs",
16
- "require": "./dist/react/index.js",
17
- "types": "./dist/react/index.d.ts"
18
- }
18
+ "require": "./dist/react/index.js"
19
+ },
20
+ "./react/styles.css": "./dist/react/styles.css"
19
21
  },
22
+ "sideEffects": [
23
+ "*.css",
24
+ "dist/react/styles.css",
25
+ "dist/react/index.js",
26
+ "dist/react/index.mjs"
27
+ ],
20
28
  "files": [
21
29
  "dist",
22
30
  "README.md"
23
31
  ],
24
32
  "scripts": {
25
- "build": "tsup",
33
+ "build:css": "tailwindcss -i ./src/react/styles.css -o ./dist/react/styles.css --minify",
34
+ "build:js": "tsup",
35
+ "build": "npm run build:js && npm run build:css",
26
36
  "dev": "tsup --watch",
27
37
  "type-check": "tsc --noEmit",
28
38
  "prepublishOnly": "npm run build",
@@ -44,14 +54,22 @@
44
54
  "author": "",
45
55
  "license": "MIT",
46
56
  "peerDependencies": {
47
- "react": ">=18.0.0",
48
- "@solana/web3.js": "^1.95.0",
57
+ "@ant-design/icons": "^5.6.1",
49
58
  "@solana/spl-token": "^0.4.0",
50
- "ethers": "^6.0.0"
59
+ "@solana/web3.js": "^1.95.0",
60
+ "antd": "^5.29.1",
61
+ "ethers": "^6.0.0",
62
+ "react": ">=18.0.0"
51
63
  },
52
64
  "peerDependenciesMeta": {
53
65
  "react": {
54
66
  "optional": true
67
+ },
68
+ "antd": {
69
+ "optional": true
70
+ },
71
+ "@ant-design/icons": {
72
+ "optional": true
55
73
  }
56
74
  },
57
75
  "dependencies": {
@@ -59,8 +77,13 @@
59
77
  "zod": "^3.22.0"
60
78
  },
61
79
  "devDependencies": {
80
+ "@ant-design/icons": "^5.6.1",
62
81
  "@types/node": "^20.0.0",
63
82
  "@types/react": "^18.0.0",
83
+ "antd": "^5.29.1",
84
+ "autoprefixer": "^10.4.22",
85
+ "postcss": "^8.5.6",
86
+ "tailwindcss": "^3.4.18",
64
87
  "tsup": "^8.0.0",
65
88
  "typescript": "^5.0.0"
66
89
  }
@@ -1,152 +0,0 @@
1
- /**
2
- * WalletConnect Component
3
- *
4
- * Pre-built wallet connection UI component with inline styles
5
- */
6
-
7
- 'use client';
8
-
9
- import React, {useState} from 'react';
10
- import {NetworkType} from '../../types';
11
- import {formatAddress, getNetworkDisplayName, getWalletInstallUrl, isWalletInstalled,} from '../../utils';
12
- import {useWallet} from '../hooks/useWalletStore';
13
- import {
14
- buttonsContainerStyle,
15
- containerStyle,
16
- getAddressStyle,
17
- getConnectButtonStyle,
18
- getDisconnectButtonStyle,
19
- getErrorStyle,
20
- getHintStyle,
21
- getInstallLinkStyle,
22
- getLabelStyle,
23
- getSectionStyle,
24
- getTitleStyle,
25
- walletActionsStyle,
26
- walletAddressStyle,
27
- walletOptionStyle,
28
- } from '../styles/inline-styles';
29
-
30
- export interface WalletConnectProps {
31
- supportedNetworks?: NetworkType[];
32
- className?: string;
33
- onConnect?: (address: string, networkType: NetworkType) => void;
34
- onDisconnect?: () => void;
35
- }
36
-
37
- /**
38
- * Pre-built wallet connection component
39
- *
40
- * @example
41
- * ```tsx
42
- * import { WalletConnect } from '../react';
43
- *
44
- * function App() {
45
- * return (
46
- * <WalletConnect
47
- * supportedNetworks={[NetworkType.SOLANA, NetworkType.EVM]}
48
- * onConnect={(address, network) => console.log('Connected:', address)}
49
- * />
50
- * );
51
- * }
52
- * ```
53
- */
54
- export function WalletConnect({
55
- supportedNetworks = [NetworkType.SOLANA, NetworkType.EVM],
56
- className = '',
57
- onConnect,
58
- onDisconnect,
59
- }: WalletConnectProps) {
60
- const {address, networkType, isConnecting, error, connect, disconnect} = useWallet();
61
- const [hoveredButton, setHoveredButton] = useState<string | null>(null);
62
- const [hoveredLink, setHoveredLink] = useState<string | null>(null);
63
-
64
- const handleConnect = async (network: NetworkType) => {
65
- try {
66
- await connect(network);
67
- // Note: address state won't be updated yet due to async setState
68
- // The parent component will re-render when address updates
69
- } catch (err) {
70
- // Error is already set in hook
71
- }
72
- };
73
-
74
- const handleDisconnect = () => {
75
- disconnect();
76
- onDisconnect?.();
77
- };
78
-
79
- return (
80
- <div style={{ ...containerStyle, ...(className ? {} : {}) }} className={className}>
81
- {!address ? (
82
- <div style={getSectionStyle()}>
83
- <h3 style={getTitleStyle()}>Connect Wallet</h3>
84
-
85
- {supportedNetworks.length === 0 ? (
86
- <p style={getHintStyle()}>Please install a supported wallet extension</p>
87
- ) : (
88
- <div style={buttonsContainerStyle}>
89
- {supportedNetworks.map((network) => {
90
- const installed = isWalletInstalled(network);
91
- return (
92
- <div key={network} style={walletOptionStyle}>
93
- <button
94
- style={getConnectButtonStyle(isConnecting || !installed, hoveredButton === network)}
95
- onClick={() => handleConnect(network)}
96
- disabled={isConnecting || !installed}
97
- onMouseEnter={() => setHoveredButton(network)}
98
- onMouseLeave={() => setHoveredButton(null)}
99
- >
100
- {isConnecting ? 'Connecting...' : getNetworkDisplayName(network)}
101
- </button>
102
- {!installed && (
103
- <a
104
- href={getWalletInstallUrl(network)}
105
- target="_blank"
106
- rel="noopener noreferrer"
107
- style={getInstallLinkStyle(hoveredLink === network)}
108
- onMouseEnter={() => setHoveredLink(network)}
109
- onMouseLeave={() => setHoveredLink(null)}
110
- >
111
- Install Wallet
112
- </a>
113
- )}
114
- </div>
115
- );
116
- })}
117
- </div>
118
- )}
119
-
120
- {error && <p style={getErrorStyle()}>{error}</p>}
121
-
122
- <p style={getHintStyle()}>
123
- To switch accounts, please change it in your wallet extension
124
- </p>
125
- </div>
126
- ) : (
127
- <div style={getSectionStyle()}>
128
- <div style={walletAddressStyle}>
129
- <span style={getLabelStyle()}>
130
- Connected {networkType && `(${getNetworkDisplayName(networkType)})`}
131
- </span>
132
- <span style={getAddressStyle()}>{formatAddress(address)}</span>
133
- </div>
134
- <div style={walletActionsStyle}>
135
- <button
136
- style={getDisconnectButtonStyle(hoveredButton === 'disconnect')}
137
- onClick={handleDisconnect}
138
- onMouseEnter={() => setHoveredButton('disconnect')}
139
- onMouseLeave={() => setHoveredButton(null)}
140
- >
141
- Disconnect
142
- </button>
143
- </div>
144
- <p style={getHintStyle()}>
145
- Switch account in your wallet to change address
146
- </p>
147
- </div>
148
- )}
149
- </div>
150
- );
151
- }
152
-
@@ -1,109 +0,0 @@
1
- /**
2
- * usePayment Hook
3
- *
4
- * React hook for payment state management
5
- * Provides state only - you control the payment flow
6
- */
7
-
8
- import {useCallback, useState} from 'react';
9
-
10
- export interface UsePaymentReturn {
11
- // State
12
- isProcessing: boolean;
13
- result: any;
14
- error: string | null;
15
-
16
- // State setters
17
- setIsProcessing: (value: boolean) => void;
18
- setResult: (value: any) => void;
19
- setError: (value: string | null) => void;
20
-
21
- // Helpers
22
- clearResult: () => void;
23
- clearError: () => void;
24
- reset: () => void;
25
- }
26
-
27
- /**
28
- * Hook for managing payment state
29
- *
30
- * This hook only manages state - you control the payment logic.
31
- * Use SDK's handleSvmPayment/handleEvmPayment directly for full control.
32
- *
33
- * @example
34
- * ```tsx
35
- * import { usePayment, useWallet } from '../react';
36
- * import { handleSvmPayment } from '@/app/sdk';
37
- *
38
- * function PaymentButton() {
39
- * const { networkType } = useWallet();
40
- * const { isProcessing, setIsProcessing, result, setResult, error, setError } = usePayment();
41
- *
42
- * const handlePay = async () => {
43
- * if (!networkType) return;
44
- *
45
- * setIsProcessing(true);
46
- * setError(null);
47
- *
48
- * try {
49
- * const response = await handleSvmPayment('/api/endpoint', {
50
- * wallet: window.solana,
51
- * network: 'solana-devnet',
52
- * });
53
- *
54
- * const data = await response.json();
55
- * setResult(data);
56
- *
57
- * // Your custom logic here
58
- * console.log('Payment success!');
59
- * } catch (err: any) {
60
- * setError(err.message);
61
- * } finally {
62
- * setIsProcessing(false);
63
- * }
64
- * };
65
- *
66
- * return (
67
- * <div>
68
- * <button onClick={handlePay} disabled={isProcessing}>
69
- * {isProcessing ? 'Processing...' : 'Pay'}
70
- * </button>
71
- * {error && <p>Error: {error}</p>}
72
- * {result && <pre>{JSON.stringify(result, null, 2)}</pre>}
73
- * </div>
74
- * );
75
- * }
76
- * ```
77
- */
78
- export function usePayment(): UsePaymentReturn {
79
- const [isProcessing, setIsProcessing] = useState(false);
80
- const [result, setResult] = useState<any>(null);
81
- const [error, setError] = useState<string | null>(null);
82
-
83
- const clearResult = useCallback(() => {
84
- setResult(null);
85
- }, []);
86
-
87
- const clearError = useCallback(() => {
88
- setError(null);
89
- }, []);
90
-
91
- const reset = useCallback(() => {
92
- setIsProcessing(false);
93
- setResult(null);
94
- setError(null);
95
- }, []);
96
-
97
- return {
98
- isProcessing,
99
- result,
100
- error,
101
- setIsProcessing,
102
- setResult,
103
- setError,
104
- clearResult,
105
- clearError,
106
- reset,
107
- };
108
- }
109
-
@@ -1,128 +0,0 @@
1
- /**
2
- * usePaymentInfo Hook
3
- *
4
- * React hook for fetching payment information from endpoint
5
- */
6
-
7
- import {useEffect, useState} from 'react';
8
- import type {PaymentRequirements} from 'x402/types';
9
- import {NetworkType} from '../../types';
10
- import {getSupportedNetworkTypes, parsePaymentRequired} from '../../utils';
11
- import {PROD_BACK_URL} from "../../types/common";
12
-
13
- export interface UsePaymentInfoReturn {
14
- // State
15
- paymentInfo: PaymentRequirements[] | null;
16
- supportedNetworks: NetworkType[];
17
- isLoading: boolean;
18
- error: string | null;
19
-
20
- // Actions
21
- refetch: () => Promise<void>;
22
- }
23
-
24
- /**
25
- * Hook for fetching payment information
26
- *
27
- * @param endpoint - API endpoint to fetch payment info from
28
- * @param merchantId - @see our website to apply
29
- * @param additionalParams - Optional additional parameters to send with the request (default: {})
30
- *
31
- * @example
32
- * ```tsx
33
- * function PaymentInfo() {
34
- * const { paymentInfo, supportedNetworks, isLoading } = usePaymentInfo('/api/protected');
35
- *
36
- * if (isLoading) return <p>Loading...</p>;
37
- *
38
- * return (
39
- * <div>
40
- * <p>Supported networks:</p>
41
- * {supportedNetworks.map(net => <span key={net}>{net}</span>)}
42
- * </div>
43
- * );
44
- * }
45
- * ```
46
- *
47
- * @example
48
- * ```tsx
49
- * // With additional parameters
50
- * function PaymentInfo() {
51
- * const { paymentInfo } = usePaymentInfo(
52
- * 'merchant-id',
53
- * '/api/protected',
54
- * { userId: '123', customField: 'value' }
55
- * );
56
- * }
57
- * ```
58
- */
59
- export function usePaymentInfo(
60
- merchantId: string,
61
- endpoint: string = PROD_BACK_URL,
62
- additionalParams?: Record<string, any>
63
- ): UsePaymentInfoReturn {
64
- const [paymentInfo, setPaymentInfo] = useState<PaymentRequirements[] | null>(null);
65
- const [supportedNetworks, setSupportedNetworks] = useState<NetworkType[]>([]);
66
- const [isLoading, setIsLoading] = useState(true);
67
- const [error, setError] = useState<string | null>(null);
68
-
69
- const fetchPaymentInfo = async () => {
70
- setIsLoading(true);
71
- setError(null);
72
-
73
- try {
74
- // 使用新变量而不是修改参数
75
- const fullEndpoint = `${endpoint}/${merchantId}`;
76
-
77
- // 准备请求配置
78
- const requestInit: RequestInit = {
79
- method: 'POST',
80
- ...(additionalParams && Object.keys(additionalParams).length > 0
81
- ? {
82
- body: JSON.stringify(additionalParams),
83
- headers: {
84
- 'Content-Type': 'application/json',
85
- },
86
- }
87
- : {}),
88
- };
89
-
90
- const response = await fetch(fullEndpoint, requestInit);
91
-
92
- if (response.status === 402) {
93
- const body = await response.json();
94
- const payment = parsePaymentRequired(body);
95
-
96
- if (payment) {
97
- setPaymentInfo(payment);
98
-
99
- const networks = getSupportedNetworkTypes(payment);
100
- setSupportedNetworks(networks);
101
- }
102
- } else {
103
- // No payment required
104
- setPaymentInfo(null);
105
- setSupportedNetworks([]);
106
- }
107
- } catch (err: any) {
108
- setError(err.message || 'Failed to fetch payment info');
109
- } finally {
110
- setIsLoading(false);
111
- }
112
- };
113
-
114
- useEffect(() => {
115
- fetchPaymentInfo();
116
- // Note: additionalParams is not in dependencies to avoid unnecessary re-fetches
117
- // If you need dynamic additionalParams, wrap it with useMemo or use refetch() manually
118
- }, [endpoint, merchantId]);
119
-
120
- return {
121
- paymentInfo,
122
- supportedNetworks,
123
- isLoading,
124
- error,
125
- refetch: fetchPaymentInfo,
126
- };
127
- }
128
-