mecha-pay 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.
- package/README.md +201 -0
- package/package.json +44 -0
- package/src/PricingTable.css +229 -0
- package/src/PricingTable.d.ts +32 -0
- package/src/PricingTable.jsx +137 -0
- package/src/api.d.ts +33 -0
- package/src/api.js +61 -0
- package/src/index.d.ts +37 -0
- package/src/index.js +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Mecha-Pay Pricing Table
|
|
2
|
+
|
|
3
|
+
A beautiful, responsive React component for displaying Mecha-Pay pricing plans. **TypeScript supported!**
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mecha-pay
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Simply provide three required props: `apiKey`, `planId`, and `userId`. That's it!
|
|
14
|
+
|
|
15
|
+
```jsx
|
|
16
|
+
import React from 'react';
|
|
17
|
+
import { PricingTable } from 'mecha-pay';
|
|
18
|
+
|
|
19
|
+
const App = () => {
|
|
20
|
+
const userId = "user_12345"; // Get from your auth system
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<PricingTable
|
|
24
|
+
apiKey="mp_live_your_api_key_here"
|
|
25
|
+
planId="0xefdc..."
|
|
26
|
+
userId={userId}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default App;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### TypeScript
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import React from 'react';
|
|
38
|
+
import { PricingTable } from 'mecha-pay';
|
|
39
|
+
|
|
40
|
+
const App: React.FC = () => {
|
|
41
|
+
const userId = "user_12345";
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<PricingTable
|
|
45
|
+
apiKey="mp_live_your_api_key_here"
|
|
46
|
+
planId="0xefdc..."
|
|
47
|
+
userId={userId}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default App;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### With Error Handling
|
|
56
|
+
|
|
57
|
+
```jsx
|
|
58
|
+
<PricingTable
|
|
59
|
+
apiKey="mp_live_your_api_key_here"
|
|
60
|
+
planId="0xefdc..."
|
|
61
|
+
userId={userId}
|
|
62
|
+
onError={(error) => {
|
|
63
|
+
console.error('Failed to load plan:', error);
|
|
64
|
+
// Handle error (e.g., show notification)
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Next.js Example
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
import { PricingTable } from 'mecha-pay';
|
|
73
|
+
import { useUser } from '@/hooks/useAuth';
|
|
74
|
+
|
|
75
|
+
export default function PricingPage() {
|
|
76
|
+
const { userId } = useUser();
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<PricingTable
|
|
80
|
+
apiKey={process.env.NEXT_PUBLIC_MECHAPAY_API_KEY}
|
|
81
|
+
planId="0xefdc..."
|
|
82
|
+
userId={userId}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### TypeScript
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import React from 'react';
|
|
92
|
+
import { PricingTable } from 'mecha-pay';
|
|
93
|
+
|
|
94
|
+
const App: React.FC = () => {
|
|
95
|
+
return (
|
|
96
|
+
<PricingTable
|
|
97
|
+
apiKey="mp_live_your_api_key_here"
|
|
98
|
+
onError={(error) => console.error(error)}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## How It Works
|
|
105
|
+
|
|
106
|
+
1. **Provide 3 Required Props:**
|
|
107
|
+
- `apiKey` - Your Mecha-Pay API key
|
|
108
|
+
- `planId` - The plan ID to display
|
|
109
|
+
- `userId` - Current user's ID
|
|
110
|
+
|
|
111
|
+
2. **Component Automatically:**
|
|
112
|
+
- Fetches plan details from `http://localhost:3000/api/v1/plans/{planId}`
|
|
113
|
+
- Displays pricing card with features
|
|
114
|
+
- Generates payment link: `http://localhost:3000/pay/{planId}?userId={userId}&successUrl={currentURL}`
|
|
115
|
+
|
|
116
|
+
3. **User Clicks "Subscribe Now":**
|
|
117
|
+
- Redirected to: `http://localhost:3000/pay/0xefdc...?userId=user_12345&successUrl=https://yoursite.com`
|
|
118
|
+
|
|
119
|
+
## API
|
|
120
|
+
|
|
121
|
+
### Required Props
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Description |
|
|
124
|
+
|------|------|-------------|
|
|
125
|
+
| apiKey | `string` | Your Mecha-Pay API key (e.g., `"mp_live_..."`) |
|
|
126
|
+
| planId | `string` | The plan ID to fetch (e.g., `"0xefdc..."`) |
|
|
127
|
+
| userId | `string` | User ID for payment link generation |
|
|
128
|
+
|
|
129
|
+
### Optional Props
|
|
130
|
+
|
|
131
|
+
| Prop | Type | Description |
|
|
132
|
+
|------|------|-------------|
|
|
133
|
+
| onError | `(error: Error) => void` | Error callback function |
|
|
134
|
+
|
|
135
|
+
## TypeScript Types
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface Feature {
|
|
139
|
+
title: string;
|
|
140
|
+
description?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface PlanMetadata {
|
|
144
|
+
name: string;
|
|
145
|
+
description: string;
|
|
146
|
+
features: Feature[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface Plan {
|
|
150
|
+
planId: string;
|
|
151
|
+
price: string;
|
|
152
|
+
duration: string;
|
|
153
|
+
metadata: PlanMetadata;
|
|
154
|
+
// activeSubscribers is returned by API but not used by component
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## API Response Structure
|
|
159
|
+
|
|
160
|
+
The component fetches from `http://localhost:3000/api/v1/plans/{planId}` with response:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"planId": "0xefdc...",
|
|
165
|
+
"price": "5000000",
|
|
166
|
+
"duration": "2592000",
|
|
167
|
+
"metadata": {
|
|
168
|
+
"name": "Pro Merchant",
|
|
169
|
+
"description": "Premium subscription plan",
|
|
170
|
+
"features": [
|
|
171
|
+
{ "title": "Analytics", "description": "Full dashboard access" }
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
"activeSubscribers": [...] // Ignored by component
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Features
|
|
179
|
+
|
|
180
|
+
- 🎨 Beautiful gradient design
|
|
181
|
+
- 📱 Fully responsive
|
|
182
|
+
- ⚡ **Zero configuration** - just 3 props!
|
|
183
|
+
- 🔄 Automatic loading and error states
|
|
184
|
+
- 🔗 Auto-generated payment links to `localhost:3000`
|
|
185
|
+
- 💎 **TypeScript support** with full type definitions
|
|
186
|
+
- ⚙️ **Next.js compatible**
|
|
187
|
+
- ♿ Accessible
|
|
188
|
+
- 🎯 Easy to customize
|
|
189
|
+
|
|
190
|
+
## Customization
|
|
191
|
+
|
|
192
|
+
The component uses CSS modules that can be overridden. Import your custom styles after the component:
|
|
193
|
+
|
|
194
|
+
```jsx
|
|
195
|
+
import { PricingTable } from 'mecha-pay';
|
|
196
|
+
import './my-custom-styles.css';
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
ISC
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mecha-pay",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A beautiful, responsive React pricing table component for Web3 applications",
|
|
5
|
+
"homepage": "https://github.com/Frank2006x/Mecha-pay#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/Frank2006x/Mecha-pay/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Frank2006x/Mecha-pay.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"author": "",
|
|
15
|
+
"main": "src/index.js",
|
|
16
|
+
"module": "src/index.js",
|
|
17
|
+
"types": "src/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"react",
|
|
23
|
+
"pricing",
|
|
24
|
+
"pricing-table",
|
|
25
|
+
"web3",
|
|
26
|
+
"component",
|
|
27
|
+
"subscription",
|
|
28
|
+
"plans",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
33
|
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"axios": "^1.6.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/react": "^18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
.pricing-table-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
gap: 2rem;
|
|
5
|
+
padding: 2rem;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
max-width: 1200px;
|
|
8
|
+
margin: 0 auto;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.pricing-card {
|
|
12
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
13
|
+
border-radius: 1rem;
|
|
14
|
+
padding: 2rem;
|
|
15
|
+
min-width: 280px;
|
|
16
|
+
max-width: 350px;
|
|
17
|
+
flex: 1;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
21
|
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
22
|
+
color: white;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.pricing-card:hover {
|
|
26
|
+
transform: translateY(-10px);
|
|
27
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.pricing-header {
|
|
31
|
+
text-align: center;
|
|
32
|
+
margin-bottom: 2rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.pricing-title {
|
|
36
|
+
font-size: 1.75rem;
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
margin: 0 0 0.5rem 0;
|
|
39
|
+
color: white;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.pricing-description {
|
|
43
|
+
font-size: 0.95rem;
|
|
44
|
+
opacity: 0.9;
|
|
45
|
+
margin: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.pricing-price {
|
|
49
|
+
text-align: center;
|
|
50
|
+
margin-bottom: 2rem;
|
|
51
|
+
padding: 1.5rem 0;
|
|
52
|
+
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
53
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.price-amount {
|
|
57
|
+
font-size: 3rem;
|
|
58
|
+
font-weight: 700;
|
|
59
|
+
display: block;
|
|
60
|
+
line-height: 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.price-duration {
|
|
64
|
+
font-size: 1rem;
|
|
65
|
+
opacity: 0.8;
|
|
66
|
+
margin-top: 0.5rem;
|
|
67
|
+
display: block;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.pricing-features {
|
|
71
|
+
flex-grow: 1;
|
|
72
|
+
margin-bottom: 2rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.features-title {
|
|
76
|
+
font-size: 1.1rem;
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
margin: 0 0 1rem 0;
|
|
79
|
+
color: white;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.features-list {
|
|
83
|
+
list-style: none;
|
|
84
|
+
padding: 0;
|
|
85
|
+
margin: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.feature-item {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: flex-start;
|
|
91
|
+
gap: 0.75rem;
|
|
92
|
+
margin-bottom: 1rem;
|
|
93
|
+
padding: 0.75rem;
|
|
94
|
+
background: rgba(255, 255, 255, 0.1);
|
|
95
|
+
border-radius: 0.5rem;
|
|
96
|
+
transition: background 0.2s ease;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.feature-item:hover {
|
|
100
|
+
background: rgba(255, 255, 255, 0.15);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.feature-icon {
|
|
104
|
+
width: 1.25rem;
|
|
105
|
+
height: 1.25rem;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
margin-top: 0.1rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.feature-content {
|
|
111
|
+
flex: 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.feature-title {
|
|
115
|
+
display: block;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
font-size: 0.95rem;
|
|
118
|
+
margin-bottom: 0.25rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.feature-description {
|
|
122
|
+
font-size: 0.85rem;
|
|
123
|
+
opacity: 0.85;
|
|
124
|
+
margin: 0;
|
|
125
|
+
line-height: 1.4;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.pricing-button {
|
|
129
|
+
display: block;
|
|
130
|
+
width: 100%;
|
|
131
|
+
padding: 1rem;
|
|
132
|
+
font-size: 1.1rem;
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
background: white;
|
|
135
|
+
color: #667eea;
|
|
136
|
+
border: none;
|
|
137
|
+
border-radius: 0.5rem;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
transition: all 0.3s ease;
|
|
140
|
+
margin-bottom: 1rem;
|
|
141
|
+
text-align: center;
|
|
142
|
+
text-decoration: none;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.pricing-button:hover {
|
|
146
|
+
background: #f7f7f7;
|
|
147
|
+
transform: scale(1.02);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.pricing-button:active {
|
|
151
|
+
transform: scale(0.98);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.pricing-button-disabled {
|
|
155
|
+
opacity: 0.5;
|
|
156
|
+
cursor: not-allowed;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.pricing-button-disabled:hover {
|
|
160
|
+
transform: none;
|
|
161
|
+
background: white;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Loading, Error, and Empty States */
|
|
165
|
+
.pricing-loading,
|
|
166
|
+
.pricing-error,
|
|
167
|
+
.pricing-empty {
|
|
168
|
+
text-align: center;
|
|
169
|
+
padding: 3rem 2rem;
|
|
170
|
+
font-size: 1.2rem;
|
|
171
|
+
color: #667eea;
|
|
172
|
+
width: 100%;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.pricing-error {
|
|
176
|
+
color: #e53e3e;
|
|
177
|
+
background: #fff5f5;
|
|
178
|
+
border-radius: 0.5rem;
|
|
179
|
+
padding: 2rem;
|
|
180
|
+
max-width: 500px;
|
|
181
|
+
margin: 0 auto;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.error-icon {
|
|
185
|
+
font-size: 2rem;
|
|
186
|
+
display: block;
|
|
187
|
+
margin-bottom: 1rem;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.error-message {
|
|
191
|
+
margin: 0;
|
|
192
|
+
font-size: 1rem;
|
|
193
|
+
color: #c53030;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.pricing-empty {
|
|
197
|
+
color: #718096;
|
|
198
|
+
font-style: italic;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.pricing-loading {
|
|
202
|
+
font-weight: 500;
|
|
203
|
+
animation: pulse 2s ease-in-out infinite;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@keyframes pulse {
|
|
207
|
+
0%, 100% {
|
|
208
|
+
opacity: 1;
|
|
209
|
+
}
|
|
210
|
+
50% {
|
|
211
|
+
opacity: 0.5;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Responsive Design */
|
|
216
|
+
@media (max-width: 768px) {
|
|
217
|
+
.pricing-table-container {
|
|
218
|
+
padding: 1rem;
|
|
219
|
+
gap: 1.5rem;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.pricing-card {
|
|
223
|
+
max-width: 100%;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.price-amount {
|
|
227
|
+
font-size: 2.5rem;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Feature {
|
|
2
|
+
title: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PlanMetadata {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
features: Feature[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Plan {
|
|
13
|
+
planId: string;
|
|
14
|
+
price: string;
|
|
15
|
+
duration: string;
|
|
16
|
+
metadata: PlanMetadata;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PricingTableProps {
|
|
20
|
+
/** Your Mecha-Pay API key (required) */
|
|
21
|
+
apiKey: string;
|
|
22
|
+
/** The plan ID to fetch and display (required) */
|
|
23
|
+
planId: string;
|
|
24
|
+
/** User ID for generating payment links (required) */
|
|
25
|
+
userId: string;
|
|
26
|
+
/** Optional error callback function */
|
|
27
|
+
onError?: (error: Error) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare const PricingTable: React.FC<PricingTableProps>;
|
|
31
|
+
|
|
32
|
+
export default PricingTable;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { getPlan } from './api';
|
|
3
|
+
import './PricingTable.css';
|
|
4
|
+
|
|
5
|
+
const BASE_URL = 'http://localhost:3000';
|
|
6
|
+
|
|
7
|
+
const PricingTable = ({ apiKey, planId, userId, onError }) => {
|
|
8
|
+
const [plan, setPlan] = useState(null);
|
|
9
|
+
const [loading, setLoading] = useState(true);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const fetchPlanData = async () => {
|
|
14
|
+
try {
|
|
15
|
+
setLoading(true);
|
|
16
|
+
setError(null);
|
|
17
|
+
|
|
18
|
+
const data = await getPlan(apiKey, planId, BASE_URL);
|
|
19
|
+
setPlan(data);
|
|
20
|
+
setLoading(false);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
setError(err.message);
|
|
23
|
+
setLoading(false);
|
|
24
|
+
if (onError) {
|
|
25
|
+
onError(err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
fetchPlanData();
|
|
31
|
+
}, [apiKey, planId, onError]);
|
|
32
|
+
|
|
33
|
+
const generatePaymentLink = () => {
|
|
34
|
+
const currentURL = typeof window !== 'undefined' ? window.location.origin : '';
|
|
35
|
+
const params = new URLSearchParams({
|
|
36
|
+
userId: userId,
|
|
37
|
+
successUrl: currentURL
|
|
38
|
+
});
|
|
39
|
+
return `${BASE_URL}/pay/${planId}?${params.toString()}`;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const formatPrice = (price) => {
|
|
43
|
+
// Convert from wei to a more readable format (assuming 18 decimals)
|
|
44
|
+
const formatted = (parseInt(price) / 1e18).toFixed(2);
|
|
45
|
+
return `$${formatted}`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const formatDuration = (seconds) => {
|
|
49
|
+
const days = Math.floor(seconds / 86400);
|
|
50
|
+
if (days >= 30) {
|
|
51
|
+
const months = Math.floor(days / 30);
|
|
52
|
+
return `${months} ${months === 1 ? 'month' : 'months'}`;
|
|
53
|
+
}
|
|
54
|
+
return `${days} ${days === 1 ? 'day' : 'days'}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (loading) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="pricing-table-container">
|
|
60
|
+
<div className="pricing-loading">Loading plan...</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (error) {
|
|
66
|
+
return (
|
|
67
|
+
<div className="pricing-table-container">
|
|
68
|
+
<div className="pricing-error">
|
|
69
|
+
<span className="error-icon">⚠️</span>
|
|
70
|
+
<p className="error-message">{error}</p>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!plan) {
|
|
77
|
+
return (
|
|
78
|
+
<div className="pricing-table-container">
|
|
79
|
+
<div className="pricing-empty">No plan found</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="pricing-table-container">
|
|
86
|
+
<div className="pricing-card">
|
|
87
|
+
<div className="pricing-header">
|
|
88
|
+
<h2 className="pricing-title">{plan.metadata.name}</h2>
|
|
89
|
+
<p className="pricing-description">{plan.metadata.description}</p>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="pricing-price">
|
|
93
|
+
<span className="price-amount">{formatPrice(plan.price)}</span>
|
|
94
|
+
<span className="price-duration">/{formatDuration(plan.duration)}</span>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div className="pricing-features">
|
|
98
|
+
<h3 className="features-title">Features</h3>
|
|
99
|
+
<ul className="features-list">
|
|
100
|
+
{plan.metadata.features.map((feature, index) => (
|
|
101
|
+
<li key={index} className="feature-item">
|
|
102
|
+
<svg
|
|
103
|
+
className="feature-icon"
|
|
104
|
+
fill="none"
|
|
105
|
+
stroke="currentColor"
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
>
|
|
108
|
+
<path
|
|
109
|
+
strokeLinecap="round"
|
|
110
|
+
strokeLinejoin="round"
|
|
111
|
+
strokeWidth={2}
|
|
112
|
+
d="M5 13l4 4L19 7"
|
|
113
|
+
/>
|
|
114
|
+
</svg>
|
|
115
|
+
<div className="feature-content">
|
|
116
|
+
<strong className="feature-title">{feature.title}</strong>
|
|
117
|
+
{feature.description && (
|
|
118
|
+
<p className="feature-description">{feature.description}</p>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</li>
|
|
122
|
+
))}
|
|
123
|
+
</ul>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<a
|
|
127
|
+
href={generatePaymentLink()}
|
|
128
|
+
className="pricing-button"
|
|
129
|
+
>
|
|
130
|
+
Subscribe Now
|
|
131
|
+
</a>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export default PricingTable;
|
package/src/api.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AxiosError } from 'axios';
|
|
2
|
+
import { Plan } from './PricingTable';
|
|
3
|
+
|
|
4
|
+
export interface ApiError {
|
|
5
|
+
message: string;
|
|
6
|
+
status?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fetches a plan from the Mecha-Pay API
|
|
11
|
+
* @param apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
|
|
12
|
+
* @param planId - The plan ID to fetch (e.g., "0xefdc...")
|
|
13
|
+
* @param baseURL - Optional base URL (defaults to http://localhost:3000)
|
|
14
|
+
* @returns Promise resolving to the plan data
|
|
15
|
+
*/
|
|
16
|
+
export declare function getPlan(
|
|
17
|
+
apiKey: string,
|
|
18
|
+
planId: string,
|
|
19
|
+
baseURL?: string
|
|
20
|
+
): Promise<Plan>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fetches all plans from the Mecha-Pay API
|
|
24
|
+
* @param apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
|
|
25
|
+
* @param baseURL - Optional base URL (defaults to http://localhost:3000)
|
|
26
|
+
* @returns Promise resolving to array of plans
|
|
27
|
+
*/
|
|
28
|
+
export declare function getPlans(
|
|
29
|
+
apiKey: string,
|
|
30
|
+
baseURL?: string
|
|
31
|
+
): Promise<Plan[]>;
|
|
32
|
+
|
|
33
|
+
export default getPlan;
|
package/src/api.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetches a plan from the Mecha-Pay API
|
|
5
|
+
* @param {string} apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
|
|
6
|
+
* @param {string} planId - The plan ID to fetch (e.g., "0xefdc...")
|
|
7
|
+
* @param {string} baseURL - Optional base URL (defaults to http://localhost:3000)
|
|
8
|
+
* @returns {Promise<Object>} The plan data
|
|
9
|
+
*/
|
|
10
|
+
export const getPlan = async (apiKey, planId, baseURL = 'http://localhost:3000') => {
|
|
11
|
+
try {
|
|
12
|
+
const response = await axios.get(`${baseURL}/api/v1/plans/${planId}`, {
|
|
13
|
+
headers: {
|
|
14
|
+
'x-api-key': apiKey,
|
|
15
|
+
'Content-Type': 'application/json'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return response.data;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.response) {
|
|
22
|
+
// Server responded with error status
|
|
23
|
+
throw new Error(`API Error: ${error.response.status} - ${error.response.data.message || error.response.statusText}`);
|
|
24
|
+
} else if (error.request) {
|
|
25
|
+
// Request made but no response received
|
|
26
|
+
throw new Error('Network Error: No response from server');
|
|
27
|
+
} else {
|
|
28
|
+
// Something else happened
|
|
29
|
+
throw new Error(`Request Error: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fetches all plans from the Mecha-Pay API
|
|
36
|
+
* @param {string} apiKey - Your Mecha-Pay API key (e.g., "mp_live_...")
|
|
37
|
+
* @param {string} baseURL - Optional base URL (defaults to http://localhost:3000)
|
|
38
|
+
* @returns {Promise<Array>} Array of plans
|
|
39
|
+
*/
|
|
40
|
+
export const getPlans = async (apiKey, baseURL = 'http://localhost:3000') => {
|
|
41
|
+
try {
|
|
42
|
+
const response = await axios.get(`${baseURL}/api/v1/plans`, {
|
|
43
|
+
headers: {
|
|
44
|
+
'x-api-key': apiKey,
|
|
45
|
+
'Content-Type': 'application/json'
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return response.data;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.response) {
|
|
52
|
+
throw new Error(`API Error: ${error.response.status} - ${error.response.data.message || error.response.statusText}`);
|
|
53
|
+
} else if (error.request) {
|
|
54
|
+
throw new Error('Network Error: No response from server');
|
|
55
|
+
} else {
|
|
56
|
+
throw new Error(`Request Error: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default getPlan;
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface Feature {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PlanMetadata {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
features: Feature[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Plan {
|
|
15
|
+
planId: string;
|
|
16
|
+
price: string;
|
|
17
|
+
duration: string;
|
|
18
|
+
metadata: PlanMetadata;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PricingTableProps {
|
|
22
|
+
/** Your Mecha-Pay API key (required) */
|
|
23
|
+
apiKey: string;
|
|
24
|
+
/** The plan ID to fetch and display (required) */
|
|
25
|
+
planId: string;
|
|
26
|
+
/** User ID for generating payment links (required) */
|
|
27
|
+
userId: string;
|
|
28
|
+
/** Optional error callback function */
|
|
29
|
+
onError?: (error: Error) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const PricingTable: FC<PricingTableProps>;
|
|
33
|
+
export default PricingTable;
|
|
34
|
+
|
|
35
|
+
// API Functions
|
|
36
|
+
export function getPlan(apiKey: string, planId: string, baseURL?: string): Promise<Plan>;
|
|
37
|
+
export function getPlans(apiKey: string, baseURL?: string): Promise<Plan[]>;
|
package/src/index.js
ADDED