Skip to content

Commit a62ac53

Browse files
committed
fixed polling
1 parent ca3da8c commit a62ac53

File tree

5 files changed

+287
-22
lines changed

5 files changed

+287
-22
lines changed

public/js/components/subscription-form.js

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,14 +419,17 @@ export class SubscriptionForm extends BaseComponent {
419419
this._error = null;
420420
this.render();
421421

422+
// Use subscription id if available, otherwise use email
423+
const requestBody = this._subscription ?
424+
{ id: this._subscription.id } :
425+
{ email: this._email };
426+
422427
const response = await fetch('/api/1/subscription-status', {
423428
method: 'POST',
424429
headers: {
425430
'Content-Type': 'application/json'
426431
},
427-
body: JSON.stringify({
428-
email: this._email
429-
})
432+
body: JSON.stringify(requestBody)
430433
});
431434

432435
if (!response.ok) {
@@ -436,7 +439,7 @@ export class SubscriptionForm extends BaseComponent {
436439

437440
const data = await response.json();
438441

439-
if (data.has_subscription) {
442+
if (data.has_subscription || data.payment_verified) {
440443
// Subscription is active, show success message
441444
this._subscription = data.subscription;
442445
this._paymentInfo = null;
@@ -445,6 +448,36 @@ export class SubscriptionForm extends BaseComponent {
445448
// Show success message
446449
this.render();
447450
this.showSuccessMessage();
451+
452+
// Generate fresh JWT token and redirect after 5 seconds
453+
if (data.payment_verified) {
454+
console.log('Payment verified, will redirect to API keys page in 5 seconds');
455+
this._showRedirectCountdown();
456+
457+
try {
458+
// Request a fresh JWT token
459+
const tokenResponse = await fetch('/api/1/auth/refresh-token', {
460+
method: 'POST',
461+
headers: {
462+
'Content-Type': 'application/json'
463+
},
464+
body: JSON.stringify({
465+
email: this._subscription.email
466+
})
467+
});
468+
469+
if (tokenResponse.ok) {
470+
console.log('JWT token refreshed successfully');
471+
}
472+
} catch (tokenError) {
473+
console.error('Error refreshing JWT token:', tokenError);
474+
}
475+
476+
// Redirect after 5 seconds
477+
setTimeout(() => {
478+
window.location.href = '/views/api-keys.html';
479+
}, 5000);
480+
}
448481
} else {
449482
// Subscription is not active yet
450483
this._loading = false;
@@ -475,6 +508,7 @@ export class SubscriptionForm extends BaseComponent {
475508
<p><strong>Plan:</strong> ${this._subscription.plan === 'monthly' ? 'Monthly' : 'Yearly'}</p>
476509
<p><strong>Expiration Date:</strong> ${new Date(this._subscription.expiration_date).toLocaleDateString()}</p>
477510
<p>You can view your API usage and manage your subscription in your account dashboard.</p>
511+
<p id="redirect-message" style="margin-top: 15px; font-weight: bold;"></p>
478512
</div>
479513
</div>
480514
`);
@@ -500,6 +534,25 @@ export class SubscriptionForm extends BaseComponent {
500534
this.render();
501535
}
502536

537+
/**
538+
* Display redirect countdown message
539+
* @private
540+
*/
541+
_showRedirectCountdown() {
542+
let seconds = 5;
543+
const updateCountdown = () => {
544+
const redirectMessage = this.$('#redirect-message');
545+
if (redirectMessage) {
546+
redirectMessage.textContent = `Redirecting to API keys page in ${seconds} seconds...`;
547+
if (seconds > 0) {
548+
seconds--;
549+
setTimeout(updateCountdown, 1000);
550+
}
551+
}
552+
};
553+
updateCountdown();
554+
}
555+
503556
/**
504557
* Generate QR code for payment
505558
* @private

src/routes/auth.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { supabase } from '../utils/supabase.js';
2+
import { errorUtils } from '../utils/error-utils.js';
3+
4+
/**
5+
* Route handler for refreshing JWT token
6+
* @param {Object} c - Hono context
7+
* @returns {Response} - JSON response with token
8+
*/
9+
export async function refreshTokenHandler(c) {
10+
try {
11+
// Get email from request body
12+
const { email } = await c.req.json();
13+
14+
if (!email) {
15+
return c.json({ error: 'Email is required' }, 400);
16+
}
17+
18+
console.log(`Refreshing JWT token for ${email}`);
19+
20+
// Check if user exists in the database
21+
const { data: userData, error: userError } = await supabase
22+
.from('users')
23+
.select('id, email')
24+
.eq('email', email)
25+
.single();
26+
27+
if (userError || !userData) {
28+
console.error('User not found:', userError);
29+
return c.json({ error: 'User not found' }, 404);
30+
}
31+
32+
// Create a new session for the user
33+
const { data: sessionData, error: sessionError } = await supabase.auth.admin.createSession({
34+
user_id: userData.id,
35+
email: userData.email
36+
});
37+
38+
if (sessionError) {
39+
console.error('Error creating session:', sessionError);
40+
return c.json({ error: 'Failed to create session' }, 500);
41+
}
42+
43+
return c.json({
44+
success: true,
45+
message: 'JWT token refreshed successfully',
46+
session: {
47+
access_token: sessionData.session.access_token,
48+
refresh_token: sessionData.session.refresh_token,
49+
expires_at: sessionData.session.expires_at
50+
}
51+
});
52+
} catch (error) {
53+
console.error('Error in refresh token handler:', error);
54+
return errorUtils.handleError(error, c);
55+
}
56+
}
57+
58+
/**
59+
* Route configuration for auth endpoints
60+
*/
61+
export const refreshTokenRoute = {
62+
method: 'POST',
63+
path: '/api/1/auth/refresh-token',
64+
handler: refreshTokenHandler
65+
};

src/routes/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
paymentLogsRoute
1414
} from './subscription.js';
1515
import { apiKeyRoutes } from './api-keys.js';
16+
import { refreshTokenRoute } from './auth.js';
1617
import { authMiddleware } from '../middleware/auth-middleware.js';
1718

1819
// Routes that require authentication
@@ -40,7 +41,8 @@ const publicRoutes = [
4041
subscriptionRoute,
4142
paymentCallbackRoute,
4243
subscriptionStatusRoute,
43-
paymentLogsRoute
44+
paymentLogsRoute,
45+
refreshTokenRoute
4446
];
4547

4648
/**

src/routes/subscription.js

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { paymentService } from '../services/payment-service.js';
22
import { errorUtils } from '../utils/error-utils.js';
3+
import { supabase } from '../utils/supabase.js';
4+
import {
5+
getBitcoinAddressBalance,
6+
getEthereumAddressBalance,
7+
getSolanaAddressBalance,
8+
getUsdcAddressBalance
9+
} from '../utils/tatum.js';
310

411
/**
512
* Route handler for creating a subscription
@@ -125,16 +132,25 @@ export async function paymentCallbackHandler(c) {
125132
* @param {Object} c - Hono context
126133
* @returns {Response} - JSON response with subscription status
127134
*/
135+
/**
136+
* Route handler for checking subscription status and verifying cryptocurrency payments
137+
* @param {Object} c - Hono context
138+
* @returns {Response} - JSON response with subscription status
139+
*/
128140
export async function subscriptionStatusHandler(c) {
129141
try {
130-
const { email } = await c.req.json();
142+
// Get request body or parameters
143+
const body = await c.req.json();
144+
const { id, email } = body;
131145

132-
if (!email) {
133-
return c.json({ error: 'Email is required' }, 400);
146+
if (!id && !email) {
147+
return c.json({ error: 'Subscription ID or email is required' }, 400);
134148
}
135149

136-
// Get subscription details
137-
const subscription = await paymentService.getSubscription(email);
150+
// Get subscription details from the database
151+
const subscription = id
152+
? await paymentService.getSubscriptionById(id)
153+
: await paymentService.getSubscription(email);
138154

139155
if (!subscription) {
140156
return c.json({
@@ -143,12 +159,90 @@ export async function subscriptionStatusHandler(c) {
143159
});
144160
}
145161

146-
// Check if subscription is active
147-
const isActive = subscription.status === 'active' &&
162+
console.log('Retrieved subscription:', JSON.stringify(subscription));
163+
164+
// Check cryptocurrency balance using Tatum API to verify payment
165+
let paymentVerified = false;
166+
let balance = null;
167+
let requiredAmount = subscription.crypto_amount || 0;
168+
169+
try {
170+
const paymentAddress = subscription.payment_address;
171+
const cryptoCurrency = subscription.crypto_currency;
172+
173+
console.log(`Verifying payment for ${cryptoCurrency} at address ${paymentAddress}`);
174+
console.log(`Required amount: ${requiredAmount} ${cryptoCurrency}`);
175+
176+
// Check balance for the appropriate cryptocurrency using Tatum API
177+
switch (cryptoCurrency) {
178+
case 'btc':
179+
balance = await getBitcoinAddressBalance(paymentAddress);
180+
break;
181+
case 'eth':
182+
balance = await getEthereumAddressBalance(paymentAddress);
183+
break;
184+
case 'sol':
185+
balance = await getSolanaAddressBalance(paymentAddress);
186+
break;
187+
case 'usdc':
188+
balance = await getUsdcAddressBalance(paymentAddress);
189+
break;
190+
default:
191+
throw new Error(`Unsupported cryptocurrency: ${cryptoCurrency}`);
192+
}
193+
194+
console.log(`Retrieved balance from Tatum:`, balance);
195+
196+
// Parse the balance based on cryptocurrency type
197+
let actualBalance = 0;
198+
if (cryptoCurrency === 'btc') {
199+
actualBalance = parseFloat(balance.incoming) - parseFloat(balance.outgoing);
200+
} else if (cryptoCurrency === 'eth' || cryptoCurrency === 'usdc') {
201+
actualBalance = parseFloat(balance.balance);
202+
} else if (cryptoCurrency === 'sol') {
203+
actualBalance = parseFloat(balance.balance);
204+
}
205+
206+
console.log(`Actual balance: ${actualBalance} ${cryptoCurrency}`);
207+
208+
// Verify if payment meets or exceeds the required amount
209+
if (actualBalance >= requiredAmount) {
210+
paymentVerified = true;
211+
console.log(`Payment verified: ${actualBalance} ${cryptoCurrency} >= ${requiredAmount} ${cryptoCurrency}`);
212+
213+
// Update subscription status to active if payment is verified
214+
if (subscription.status !== 'active') {
215+
await supabase
216+
.from('subscriptions')
217+
.update({
218+
status: 'active',
219+
updated_at: new Date().toISOString(),
220+
last_payment_date: new Date().toISOString()
221+
})
222+
.eq('id', subscription.id);
223+
224+
console.log(`Subscription ${subscription.id} status updated to active`);
225+
226+
// Update the subscription object to reflect the changes
227+
subscription.status = 'active';
228+
subscription.updated_at = new Date().toISOString();
229+
subscription.last_payment_date = new Date().toISOString();
230+
}
231+
} else {
232+
console.log(`Payment verification failed: ${actualBalance} ${cryptoCurrency} < ${requiredAmount} ${cryptoCurrency}`);
233+
}
234+
} catch (verificationError) {
235+
console.error('Error verifying payment:', verificationError);
236+
}
237+
238+
// Check if subscription is active based on status and expiration date
239+
const isActive = (subscription.status === 'active' || paymentVerified) &&
148240
new Date(subscription.expiration_date) > new Date();
149241

150242
return c.json({
151243
has_subscription: isActive,
244+
payment_verified: paymentVerified,
245+
balance: balance,
152246
subscription: {
153247
id: subscription.id,
154248
email: subscription.email,
@@ -159,10 +253,16 @@ export async function subscriptionStatusHandler(c) {
159253
start_date: subscription.start_date,
160254
expiration_date: subscription.expiration_date,
161255
payment_method: subscription.payment_method,
256+
payment_address: subscription.payment_address,
257+
crypto_amount: subscription.crypto_amount,
258+
crypto_currency: subscription.crypto_currency,
259+
exchange_rate_usd: subscription.exchange_rate_usd,
260+
last_payment_date: subscription.last_payment_date,
162261
is_active: isActive
163262
}
164263
});
165264
} catch (error) {
265+
console.error('Error in subscription status handler:', error);
166266
return errorUtils.handleError(error, c);
167267
}
168268
}

0 commit comments

Comments
 (0)