@@ -261,11 +261,14 @@ const getUserTransactionHistory = async (user_id, limit = 10, page = 1, groupbyd
261261 if ( groupbyday ) {
262262 query = `
263263 SELECT
264+ NULL AS id,
264265 t.user_id,
265266 t.item_id,
266267 SUM(t.quantity) AS quantity,
267268 SUM(t.price_at_transaction * t.quantity) / SUM(t.quantity) / 100.0 AS price_at_transaction,
268269 t.transaction_timestamp,
270+ t.status,
271+ 0 AS can_refund,
269272 t.custom_item_text,
270273 i.uuid AS item_uuid,
271274 i.name AS item_name,
@@ -285,19 +288,29 @@ const getUserTransactionHistory = async (user_id, limit = 10, page = 1, groupbyd
285288 WHERE
286289 t.user_id = ?
287290 GROUP BY
288- DATE(t.transaction_timestamp), t.item_id, t.custom_item_text, t.user_id, i.uuid, i.name, u.uuid, u.name, u.username, initiator.name, initiator.username
291+ DATE(t.transaction_timestamp), t.item_id, t.status, t. custom_item_text, t.user_id, i.uuid, i.name, u.uuid, u.name, u.username, initiator.name, initiator.username
289292 ORDER BY
290293 transaction_timestamp DESC
291294 LIMIT ? OFFSET ?
292295 ` ;
293296 } else {
294297 query = `
295298 SELECT
299+ t.id,
296300 t.user_id,
297301 t.item_id,
298302 t.quantity,
299303 t.price_at_transaction / 100.0 AS price_at_transaction,
300304 t.transaction_timestamp,
305+ t.status,
306+ CASE
307+ WHEN t.status = 0
308+ AND NOT (i.uuid = '13620506-b9f8-44d7-a9ff-d1b58ddee93f' AND t.custom_item_text IS NULL)
309+ AND (SELECT setting_value FROM app_settings WHERE setting_key = 'AUTO_REFUNDS_ACTIVE') = 'true'
310+ AND datetime(t.transaction_timestamp, '+' || COALESCE((SELECT setting_value FROM app_settings WHERE setting_key = 'AUTO_REFUNDS_MINUTES'), '0') || ' minutes') >= datetime('now')
311+ THEN 1
312+ ELSE 0
313+ END AS can_refund,
301314 t.custom_item_text,
302315 i.uuid AS item_uuid,
303316 i.name AS item_name,
@@ -338,11 +351,14 @@ const getAllTransactionHistory = async (limit = 10, page = 1, groupbyday = true)
338351 if ( groupbyday ) {
339352 query = `
340353 SELECT
354+ NULL AS id,
341355 t.user_id,
342356 t.item_id,
343357 SUM(t.quantity) AS quantity,
344358 SUM(t.price_at_transaction * t.quantity) / SUM(t.quantity) / 100.0 AS price_at_transaction,
345359 t.transaction_timestamp,
360+ t.status,
361+ 0 AS can_refund,
346362 t.custom_item_text,
347363 i.uuid AS item_uuid,
348364 i.name AS item_name,
@@ -360,7 +376,7 @@ const getAllTransactionHistory = async (limit = 10, page = 1, groupbyday = true)
360376 JOIN
361377 users initiator ON t.initiator_id = initiator.id
362378 GROUP BY
363- DATE(t.transaction_timestamp), t.item_id, t.custom_item_text, t.user_id, i.uuid, i.name, u.uuid, u.name, u.username, initiator.name, initiator.username
379+ DATE(t.transaction_timestamp), t.item_id, t.status, t. custom_item_text, t.user_id, i.uuid, i.name, u.uuid, u.name, u.username, initiator.name, initiator.username
364380 ORDER BY
365381 transaction_timestamp DESC
366382 LIMIT ? OFFSET ?
@@ -369,11 +385,19 @@ const getAllTransactionHistory = async (limit = 10, page = 1, groupbyday = true)
369385 } else {
370386 query = `
371387 SELECT
388+ t.id,
372389 t.user_id,
373390 t.item_id,
374391 t.quantity,
375392 t.price_at_transaction / 100.0 AS price_at_transaction,
376393 t.transaction_timestamp,
394+ t.status,
395+ CASE
396+ WHEN t.status = 0
397+ AND NOT (i.uuid = '13620506-b9f8-44d7-a9ff-d1b58ddee93f' AND t.custom_item_text IS NULL)
398+ THEN 1
399+ ELSE 0
400+ END AS can_refund,
377401 t.custom_item_text,
378402 i.uuid AS item_uuid,
379403 i.name AS item_name,
@@ -400,6 +424,71 @@ const getAllTransactionHistory = async (limit = 10, page = 1, groupbyday = true)
400424 return db . prepare ( query ) . all ( ...params ) ;
401425}
402426
427+ const isAutoRefundAvailable = ( ) => {
428+ const active = db . prepare ( 'SELECT setting_value FROM app_settings WHERE setting_key = ?' ) . get ( 'AUTO_REFUNDS_ACTIVE' ) ;
429+ return active ?. setting_value === 'true' ;
430+ } ;
431+
432+ const getAutoRefundMinutes = ( ) => {
433+ const setting = db . prepare ( 'SELECT setting_value FROM app_settings WHERE setting_key = ?' ) . get ( 'AUTO_REFUNDS_MINUTES' ) ;
434+ const minutes = Number ( setting ?. setting_value ?? 0 ) ;
435+ return Number . isFinite ( minutes ) && minutes > 0 ? minutes : 0 ;
436+ } ;
437+
438+ const refundTransaction = ( transactionId , requesterId , { force = false } = { } ) => {
439+ const runRefund = db . transaction ( ( id , userId , forceRefund ) => {
440+ const transaction = db . prepare ( `
441+ SELECT
442+ t.id,
443+ t.user_id,
444+ t.item_id,
445+ t.quantity,
446+ t.price_at_transaction,
447+ t.transaction_timestamp,
448+ t.status,
449+ t.custom_item_text,
450+ i.uuid AS item_uuid
451+ FROM transactions t
452+ JOIN items i ON i.id = t.item_id
453+ WHERE t.id = ?
454+ ` ) . get ( id ) ;
455+
456+ if ( ! transaction ) return { success : false , status : 404 , error : 'Transaction not found' } ;
457+ if ( transaction . status === 1 ) return { success : false , status : 409 , error : 'Transaction already refunded' } ;
458+ if ( transaction . item_uuid === '13620506-b9f8-44d7-a9ff-d1b58ddee93f' && ! transaction . custom_item_text ) {
459+ return { success : false , status : 400 , error : 'System transactions cannot be refunded' } ;
460+ }
461+
462+ if ( ! forceRefund ) {
463+ if ( transaction . user_id !== userId ) return { success : false , status : 404 , error : 'Transaction not found' } ;
464+ if ( ! isAutoRefundAvailable ( ) ) return { success : false , status : 403 , error : 'Auto refunds disabled' } ;
465+
466+ const minutes = getAutoRefundMinutes ( ) ;
467+ const timestamp = `${ String ( transaction . transaction_timestamp ) . replace ( ' ' , 'T' ) } Z` ;
468+ const ageMs = Date . now ( ) - new Date ( timestamp ) . getTime ( ) ;
469+ if ( ! minutes || Number . isNaN ( ageMs ) || ageMs > minutes * 60 * 1000 ) {
470+ return { success : false , status : 403 , error : 'Refund window expired' } ;
471+ }
472+ }
473+
474+ const refundAmount = transaction . price_at_transaction * transaction . quantity ;
475+ const balanceDelta = transaction . item_uuid === '13620506-b9f8-44d7-a9ff-d1b58ddee93f' && ! transaction . custom_item_text
476+ ? refundAmount * - 1
477+ : refundAmount ;
478+ db . prepare ( 'UPDATE users SET balance = balance + ? WHERE id = ?' ) . run ( balanceDelta , transaction . user_id ) ;
479+ db . prepare ( 'UPDATE items SET stock = stock + ? WHERE id = ? AND is_active = 1' ) . run ( transaction . quantity , transaction . item_id ) ;
480+ db . prepare ( 'UPDATE transactions SET status = 1 WHERE id = ?' ) . run ( transaction . id ) ;
481+ db . prepare ( `
482+ INSERT INTO refunds (user_id, transaction_id, authorizer_id, status, approved_timestamp)
483+ VALUES (?, ?, ?, 1, datetime('now'))
484+ ` ) . run ( transaction . user_id , transaction . id , userId ) ;
485+
486+ return { success : true , refundedAmount : refundAmount } ;
487+ } ) ;
488+
489+ return runRefund ( transactionId , requesterId , force ) ;
490+ } ;
491+
403492/**
404493 * Retrieves a user's favorite items.
405494 * @param {Number } user_id
@@ -578,6 +667,7 @@ module.exports = {
578667 purchaseItem,
579668 getUserTransactionHistory,
580669 getAllTransactionHistory,
670+ refundTransaction,
581671 getUserFavorites,
582672 updateUserUserName,
583673 updateUserUserNameByUUID,
0 commit comments