Skip to content

Commit a6b7c76

Browse files
committed
last fixes
1 parent becc381 commit a6b7c76

File tree

3 files changed

+151
-74
lines changed

3 files changed

+151
-74
lines changed

apps/self-hosted/DEPLOYMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ Memo: blog:YOUR_HIVE_USERNAME
464464
## Support
465465

466466
- GitHub Issues: https://github.com/ecency/vision-next/issues
467-
- Discord: https://discord.gg/ecency
467+
- Discord: https://discord.me/ecency
468468
- Hive: @ecency
469469

470470
## License

apps/self-hosted/hosting/api/payment-listener.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -462,29 +462,54 @@ class PaymentListener {
462462
}
463463

464464
private async getTransfersInBlock(blockNum: number): Promise<HiveTransfer[]> {
465+
// Use block_api.get_block (AppBase) instead of legacy condenser_api.get_block
465466
const response = await fetch(CONFIG.HIVE_API_URL, {
466467
method: 'POST',
467468
headers: { 'Content-Type': 'application/json' },
468469
body: JSON.stringify({
469470
jsonrpc: '2.0',
470-
method: 'condenser_api.get_block',
471-
params: [blockNum],
471+
method: 'block_api.get_block',
472+
params: { block_num: blockNum },
472473
id: 1,
473474
}),
474475
});
475476

476477
const data = await response.json();
477-
const block = data.result;
478478

479-
if (!block || !block.transactions) return [];
479+
// AppBase wraps the block in data.result.block
480+
const block = data.result?.block;
481+
482+
// Defensive checks for the new response shape
483+
if (!block) {
484+
console.log(`[PaymentListener] No block returned for block ${blockNum}`);
485+
return [];
486+
}
487+
488+
if (!block.transactions || !Array.isArray(block.transactions)) {
489+
return [];
490+
}
491+
492+
if (!block.transaction_ids || !Array.isArray(block.transaction_ids)) {
493+
console.log(`[PaymentListener] No transaction_ids in block ${blockNum}`);
494+
return [];
495+
}
480496

481497
const transfers: HiveTransfer[] = [];
482498

483499
// Use index to get trx_id from block.transaction_ids array
484-
// tx.transaction_id is undefined in the Hive API response
485500
for (let txIndex = 0; txIndex < block.transactions.length; txIndex++) {
486501
const tx = block.transactions[txIndex];
487-
const trxId = block.transaction_ids?.[txIndex] || '';
502+
const trxId = block.transaction_ids[txIndex];
503+
504+
// Skip if no trx_id available
505+
if (!trxId) {
506+
console.log(`[PaymentListener] Missing trx_id for tx at index ${txIndex} in block ${blockNum}`);
507+
continue;
508+
}
509+
510+
if (!tx.operations || !Array.isArray(tx.operations)) {
511+
continue;
512+
}
488513

489514
for (const op of tx.operations) {
490515
if (op[0] === 'transfer' && op[1].to === CONFIG.PAYMENT_ACCOUNT) {

apps/self-hosted/hosting/api/src/payment-listener.ts

Lines changed: 119 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,23 @@ class PaymentListener {
9898
const block = await hiveClient.database.getBlock(blockNum);
9999
if (!block || !block.transactions) return;
100100

101-
for (const tx of block.transactions) {
101+
// transaction_ids array contains the IDs - tx.transaction_id is undefined
102+
const transactionIds = (block as any).transaction_ids as string[] | undefined;
103+
104+
for (let txIndex = 0; txIndex < block.transactions.length; txIndex++) {
105+
const tx = block.transactions[txIndex];
106+
const trxId = transactionIds?.[txIndex];
107+
108+
// Skip if no trx_id available
109+
if (!trxId) {
110+
console.log(`[PaymentListener] Missing trx_id for tx at index ${txIndex} in block ${blockNum}`);
111+
continue;
112+
}
113+
102114
for (const op of tx.operations) {
103115
if (op[0] === 'transfer' && op[1].to === CONFIG.PAYMENT_ACCOUNT) {
104116
await this.processTransfer({
105-
trxId: tx.transaction_id,
117+
trxId,
106118
blockNum,
107119
from: op[1].from,
108120
to: op[1].to,
@@ -155,43 +167,55 @@ class PaymentListener {
155167
return;
156168
}
157169

158-
// Calculate months based on amount - only credit what was actually paid
159-
const monthsFromAmount = Math.floor(amount / CONFIG.MONTHLY_PRICE_HBD);
160-
161-
if (monthsFromAmount < 1) {
162-
console.log('[PaymentListener] Insufficient amount:', amount, 'HBD for', parsed.username);
163-
await this.logPayment(transfer, amount, 'failed', 0, null, 'Insufficient amount');
164-
return;
165-
}
170+
// Process based on action
171+
if (parsed.action === 'blog') {
172+
// For blog subscriptions: validate monthly payment
173+
const monthsFromAmount = Math.floor(amount / CONFIG.MONTHLY_PRICE_HBD);
166174

167-
// If user requested more months than they paid for, reject
168-
if (parsed.months > monthsFromAmount) {
169-
console.log(
170-
'[PaymentListener] Requested months exceed payment:',
171-
parsed.months,
172-
'requested but only',
173-
monthsFromAmount,
174-
'paid for',
175-
parsed.username
176-
);
177-
await this.logPayment(
178-
transfer,
179-
amount,
180-
'failed',
181-
0,
182-
null,
183-
`Requested ${parsed.months} months but only paid for ${monthsFromAmount}`
184-
);
185-
return;
186-
}
175+
if (monthsFromAmount < 1) {
176+
console.log('[PaymentListener] Insufficient amount:', amount, 'HBD for', parsed.username);
177+
await this.logPayment(transfer, amount, 'failed', 0, null, 'Insufficient amount for subscription');
178+
return;
179+
}
187180

188-
// Grant only the months that were paid for
189-
const months = monthsFromAmount;
181+
// If user requested more months than they paid for, reject
182+
if (parsed.months > monthsFromAmount) {
183+
console.log(
184+
'[PaymentListener] Requested months exceed payment:',
185+
parsed.months,
186+
'requested but only',
187+
monthsFromAmount,
188+
'paid for',
189+
parsed.username
190+
);
191+
await this.logPayment(
192+
transfer,
193+
amount,
194+
'failed',
195+
0,
196+
null,
197+
`Requested ${parsed.months} months but only paid for ${monthsFromAmount}`
198+
);
199+
return;
200+
}
190201

191-
// Process based on action
192-
if (parsed.action === 'blog') {
202+
// Grant only the months that were paid for
203+
const months = monthsFromAmount;
193204
await this.processSubscription(transfer, parsed.username, months, amount);
194205
} else if (parsed.action === 'upgrade') {
206+
// For upgrades: validate against pro upgrade price, not monthly price
207+
if (amount < CONFIG.PRO_UPGRADE_PRICE_HBD) {
208+
console.log('[PaymentListener] Insufficient amount for upgrade:', amount, 'HBD');
209+
await this.logPayment(
210+
transfer,
211+
amount,
212+
'failed',
213+
0,
214+
null,
215+
`Insufficient amount for Pro upgrade (need ${CONFIG.PRO_UPGRADE_PRICE_HBD} HBD)`
216+
);
217+
return;
218+
}
195219
await this.processUpgrade(transfer, parsed.username, amount);
196220
}
197221
}
@@ -204,46 +228,74 @@ class PaymentListener {
204228
) {
205229
console.log('[PaymentListener] Processing subscription:', username, 'for', months, 'months');
206230

207-
try {
208-
// Check if tenant exists, create if not
209-
let tenant = await TenantService.getByUsername(username);
231+
let updatedTenant: any = null;
210232

211-
if (!tenant) {
212-
// Verify Hive account exists
213-
const accountExists = await TenantService.verifyHiveAccount(username);
214-
if (!accountExists) {
215-
console.log('[PaymentListener] Hive account not found:', username);
216-
await this.logPayment(transfer, amount, 'failed', 0, null, 'Hive account not found');
233+
try {
234+
// Use transaction to ensure atomicity of payment insert and subscription update
235+
await db.transaction(async (client) => {
236+
// 1. Insert payment with 'processing' status first (serves as dedup via unique trx_id)
237+
const insertResult = await client.query(
238+
`INSERT INTO payments
239+
(tenant_id, trx_id, block_num, from_account, amount, currency, memo, status, months_credited)
240+
VALUES (NULL, $1, $2, $3, $4, 'HBD', $5, 'processing', $6)
241+
ON CONFLICT (trx_id) DO NOTHING
242+
RETURNING id`,
243+
[transfer.trxId, transfer.blockNum, transfer.from, amount, transfer.memo, months]
244+
);
245+
246+
// If no row returned, trx_id already exists (duplicate)
247+
if (insertResult.rows.length === 0) {
248+
console.log('[PaymentListener] Duplicate transaction detected:', transfer.trxId);
217249
return;
218250
}
219251

220-
tenant = await TenantService.create(username);
221-
console.log('[PaymentListener] Created new tenant:', username);
222-
}
223-
224-
// Activate/extend subscription
225-
const updatedTenant = await TenantService.activateSubscription(username, months);
226-
227-
// Generate config file
228-
await ConfigService.generateConfigFile(updatedTenant);
229-
230-
// Log successful payment
231-
await this.logPayment(
232-
transfer,
233-
amount,
234-
'processed',
235-
months,
236-
updatedTenant.subscription_expires_at
237-
);
252+
const paymentId = insertResult.rows[0].id;
253+
254+
// 2. Check if tenant exists, create if not
255+
let tenant = await TenantService.getByUsername(username);
256+
257+
if (!tenant) {
258+
// Verify Hive account exists
259+
const accountExists = await TenantService.verifyHiveAccount(username);
260+
if (!accountExists) {
261+
// Update payment to failed status
262+
await client.query(
263+
`UPDATE payments SET status = 'failed' WHERE id = $1`,
264+
[paymentId]
265+
);
266+
throw new Error('Hive account not found');
267+
}
268+
269+
tenant = await TenantService.create(username);
270+
console.log('[PaymentListener] Created new tenant:', username);
271+
}
238272

239-
console.log(
240-
'[PaymentListener] Subscription activated for',
241-
username,
242-
'until',
243-
updatedTenant.subscription_expires_at
244-
);
273+
// 3. Activate/extend subscription
274+
updatedTenant = await TenantService.activateSubscription(username, months);
275+
276+
// 4. Update payment to 'processed' with tenant_id and subscription info
277+
await client.query(
278+
`UPDATE payments
279+
SET tenant_id = $2, status = 'processed', subscription_extended_to = $3
280+
WHERE id = $1`,
281+
[paymentId, updatedTenant.id, updatedTenant.subscription_expires_at]
282+
);
283+
284+
console.log(
285+
'[PaymentListener] Subscription activated for',
286+
username,
287+
'until',
288+
updatedTenant.subscription_expires_at
289+
);
290+
});
291+
292+
// 5. Generate config file AFTER transaction commits successfully
293+
if (updatedTenant) {
294+
await ConfigService.generateConfigFile(updatedTenant);
295+
}
245296
} catch (error) {
246297
console.error('[PaymentListener] Failed to process subscription for', username, error);
298+
// Log failure (may fail if trx_id already exists, which is fine)
247299
await this.logPayment(transfer, amount, 'failed', 0, null, String(error));
248300
}
249301
}

0 commit comments

Comments
 (0)